How to call a method in another module JavaScript? - javascript

I have a class with method performing login
LoginPage.js
class loginPage {
fillCredentials(username, password) {
cy.get('[id=username]').type(username);
cy.get('[id=password]').type(password);
return this;
}
clickLogin() {
cy.contains("Login").click();
}
}
export default loginPage;
I have another spec file for testing:
login.spec.js
import {fillCredentials,clickLogin} from '../../support/PageObjects/loginPage'
describe('User Onboarding Emails', () => {
it('Verification email', () => {
cy.visit('/')
fillCredentials('username','password')
clickLogin()
});
});
However, it is giving an error of
(0 , _loginPage.fillCredentials) is not a function
I know its a wrong way of calling a method. Is there any way I can use the methods without creating an instance of class to access methods

You can do so if you make the methods static
class loginPage {
static fillCredentials(username, password) {
cy.get('[id=username]').type(username);
cy.get('[id=password]').type(password);
//return this; // you can't return "this" because there is no this for static methods
}
static clickLogin() {
cy.contains("Login").click();
}
}
export default loginPage;
import {fillCredentials,clickLogin} from '../../support/PageObjects/loginPage'
describe('User Onboarding Emails', () => {
it('Verification email', () => {
cy.visit('/')
fillCredentials('username','password')
clickLogin()
});
});
With static methods you lose this which refers to the class instance, and therefore lose the ability to chain methods,
import {fillCredentials,clickLogin} from '../../support/PageObjects/loginPage'
describe('User Onboarding Emails', () => {
it('Verification email', () => {
cy.visit('/')
fillCredentials('username','password').clickLogin() // can't do this with static
});
});
As functions instead of a class, this is the pattern
// LoginPage.js
export const fillCredentials = (username, password) => {
cy.get('[id=username]').type(username);
cy.get('[id=password]').type(password);
return this;
}
export const clickLogin = () => {
cy.contains("Login").click();
}
// login.spec (same as you have above)
import { fillCredentials, clickLogin } from '../../support/PageObjects/loginPage'
describe('User Onboarding Emails', () => {
it('Verification email', () => {
cy.visit('/')
fillCredentials('username','password')
clickLogin()
});
})
Be wary of encapsulating test code in page objects, it can lead to over-complicated code.
For example, if you now want to test what happens if password is not entered, you can't use fillCredentials because you can't omit the password parameter. So do you add a fillCredentialsWithoutPassword function? Do you modify fillCredentials to test if password is undefined?

So, what's going on is that you are mixing up the module structure with is defined by Closures with the class-instance pattern.
For this scenario (which is class-instance) the functions are NOT part of the object itself, but of it's PROTOTYPE.
So in order to get that function you should access it's prototype.
//create a class (class syntax and this one is pretty much the same)
function xx (){}
//this is what class syntax makes to create a method for the class
xx.prototype.theFunctionIwant = function (){}
//create an instance
var example = new xx()
//this is how you can spy that function in the test
xx.prototype.theFunctionIwant
//ƒ (){}
Try it out : )

The other answers given will work and solve your exact question, but I fear that making the functions static is an anti-pattern for the Page Object Model. Check out this article on how to set up a POM for Cypress. I would highly encourage you to instantiate the class, similar to below.
//LoginPage.js
export class LoginPage { ... }
//login.spec.js
import { LoginPage } from '../../support/PageObjects/loginPage';
describe('User Onboarding Emails', () => {
it('Verification email', () => {
const loginPage = new LoginPage()
cy.visit('/')
loginPage.fillCredentials('username','password')
loginPage.clickLogin()
});
});
You could also use a beforeEach() block to instantiate the variable before each test.
describe('User Onboarding Emails', () => {
let loginPage: LoginPage;
beforeEach(() => {
loginPage = new LoginPage();
});
it('Verification email', () => {
...
});
})
As an aside, it is usually preferred to name classes beginning with an uppercase (LoginPage vs. loginPage). When naming a class this way, you can easily differentiate the class vs. the instantiated variable.

Related

How to mock a specific method of a class whilst keeping the implementation of all other methods with jest when the class instance isn't accessible?

Based on this question (How to mock instance methods of a class mocked with jest.mock?), how can a specific method be mocked whilst keeping the implementation of all other methods?
There's a similar question (Jest: How to mock one specific method of a class) but this only applies if the class instance is available outside it's calling class so this wouldn't work if the class instance was inside a constructor like in this question (How to mock a constructor instantiated class instance using jest?).
For example, the Logger class is mocked to have only method1 mocked but then method2 is missing, resulting in an error:
// Logger.ts
export default Logger() {
constructor() {}
method1() {
return 'method1';
}
method2() {
return 'method2';
}
}
// Logger.test.ts
import Logger from './Logger';
jest.mock("./Logger", () => {
return {
default: class mockLogger {
method1() {
return 'mocked';
}
},
__esModule: true,
};
});
describe("Logger", () => {
it("calls logger.method1() & logger.method2 on instantiation where only method1 is mocked", () => {
const logger = new Logger(); // Assume this is called in the constructor of another object.
expect(logger.method1()).toBe('mocked');
expect(logger.method2()).toBe('method2'); // TypeError: logger.method2 is not a function.
});
});
One solution is to extend the Logger class but this results in an undefined error as the Logger is already mocked:
// ...
jest.mock("./Logger", () => {
return {
default: class mockLogger extends Logger {
override method1() {
return 'mocked';
}
},
__esModule: true,
};
});
// ...
expect(logger.method2()).toBe('method2'); // TypeError: Cannot read property 'default' of undefined
Therefore, what could be the correct way to mock only method1 but keep method2's original implementation?
You can use jest.spyOn and provide a mock implementation for method1.
// Logger.test.ts
import Logger from './Logger';
jest.spyOn(Logger.prototype, "method1").mockImplementation(() => "mocked")
describe("Logger", () => {
it("calls method1 & method2 but only method1 is mocked", () => {
const l = new Logger();
expect(l.method1()).toBe("mocked");
expect(l.method2()).toBe("method2");
})
})
But in case you have many methods and you want to mock each one of them except one single method, then you can get the original implementation of this one single method using jest.requireActual.
// Logger.test.ts
import Logger from "./Logger";
const mockMethod1 = jest.fn().mockReturnValue("mocked");
const mockMethod3 = jest.fn().mockReturnValue("mocked");
const mockMethod4 = jest.fn().mockReturnValue("mocked");
const mockMethod5 = jest.fn().mockReturnValue("mocked");
jest.mock("./Logger", () =>
jest.fn().mockImplementation(() => ({
method1: mockMethod1,
method2: jest.requireActual("./Logger").default.prototype.method2,
method3: mockMethod3,
method4: mockMethod4,
method5: mockMethod5,
}))
);
describe("Logger", () => {
it("calls all methods but only method1 is mocked", () => {
const l = new Logger();
expect(l.method1()).toBe("mocked");
expect(l.method2()).toBe("method2");
expect(l.method3()).toBe("mocked");
expect(l.method4()).toBe("mocked");
expect(l.method5()).toBe("mocked");
});
});
Note: You don't need to define an ES6 class for mocking, a constructor function also just works fine because ES6 classes are actually just syntactic sugar for constructor functions.
Mocking the prototype works:
describe("Logger", () => {
it("calls logger.method1() & logger.method2 on instantiation where only method1 is mocked", () => {
Logger.prototype.method1 = jest.fn(() => 'mocked');
const logger = new Logger();
expect(logger.method1()).toBe('mocked');
expect(logger.method2()).toBe('method2');
});
});
However, I'm not sure if this is the correct way to mock a specific method when the class instance isn't accessible so I'll leave the question open for while in case there are better solutions.

TypeError: _API.default is not a constructor with Jest tests

I have an API class that I am trying to use in a React app.
// API file
class API {
...
}
export default API;
// Other file
import API from "utils/API";
const api = new API();
And I am getting the error:
TypeError: _API.default is not a constructor
But.. it seems like my default is set?
My Jest setup is like this:
"jest": {
"setupFiles": [
"./jestSetupFile.js"
],
"testEnvironment": "jsdom",
"preset": "jest-expo",
"transformIgnorePatterns": [
"node_modules/(?!((jest-)?react-native|#react-native(-community)?)|expo(nent)?|#expo(nent)?/.*|#expo-google-fonts/.*|react-navigation|#react-navigation/.*|#unimodules/.*|unimodules|sentry-expo|native-base|react-native-svg|react-router-native/.*|#invertase/react-native-apple-authentication/.*)"
]
},
My strong guess is that this is due to a configuration of my babel, webpack or package.json.
What could be causing this?
Note, I want to be clear, this doesn't happen whatsoever in my main application, only in Jest testing
If I change it to a named export/import, I get this:
TypeError: _API.API is not a constructor
Extremely confusing behavior.
As mentioned by others, it would be helpful to see a minimum reproducible example.
However, there is one other possible cause. Are you mocking the API class in your test file at all? This problem can sometimes happen if a class is mistakenly mocked as an "object" as opposed to a function. An object cannot be instantiated with a "new" operator.
For example, say we have a class file utils/API like so:
class API {
someMethod() {
// Does stuff
}
}
export default API;
The following is an "incorrect" way to mock this class and will throw a TypeError... is not a constructor error if the class is instantiated after the mock has been created.
import API from 'utils/API';
jest.mock('utils/API', () => {
// Returns an object
return {
someMethod: () => {}
};
})
// This will throw the error
const api = new API();
The following will mock the class as a function and will accept the new operator and will not throw the error.
import API from 'utils/API';
jest.mock('utils/API', () => {
// Returns a function
return jest.fn().mockImplementation(() => ({
someMethod: () => {}
}));
})
// This will not throw an error anymore
const api = new API();
Trying adding "esModuleInterop": true, in your tsconfig.json. BY default esModuleInterop is set to false or is not set. B setting esModuleInterop to true changes the behavior of the compiler and fixes some ES6 syntax errors.
Refer the documentation here.
This was ultimately due to additional code inside the file that I was exporting the class from.
import { store } from "root/App";
if (typeof store !== "undefined") {
let storeState = store.getState();
let profile = storeState.profile;
}
At the top, outside my class for some functionality I had been working on.
This caused the class default export to fail, but only in Jest, not in my actual application.
You'll need to export it like this :
export default class API
You could try with:
utils/API.js
export default class API {
...
}
test.js
import API from "utils/API";
const api = new API();
I'm adding this because the issue I had presented the same but has a slightly different setup.
I'm not exporting the class with default, i.e.
MyClass.ts
// with default
export default MyClass {
public myMethod()
{
return 'result';
}
}
// without default, which i'm doing in some instances.
export MyClass {
public myMethod()
{
return 'result';
}
}
When you don't have the default, the import syntax changes.
In a (jest) test if you follow the convention where you do have export default MyClass(){};
then the following works.
const MOCKED_METHOD_RESULT = 'test-result'
jest.mock("MyClass.ts", () => {
// will work and let you check for constructor calls:
return jest.fn().mockImplementation(function () {
return {
myMethod: () => {
return MOCKED_METHOD_RESULT;
},
};
});
});
However, if you don't have the default and or are trying to mock other classes etc. then you need to do the following.
Note, that the {get MyClass(){}} is the critical part, i believe you can swap out the jest.fn().mockImplementation() in favour of jest.fn(()=>{})
jest.mock("MyClass.ts", () => ({
get MyClass() {
return jest.fn().mockImplementation(function () {
return {
myMethod: () => {
return MOCKED_METHOD_RESULT;
},
};
});
},
}));
So the issue is the way in which you access the contents of the class your mocking. And the get part allows you to properly define class exports.
I resolved this error by using below code.
jest.mock('YOUR_API_PATH', () => ({
__esModule: true,
default: // REPLICATE YOUR API CONSTRUCTOR BEHAVIOUR HERE BY ADDING CLASS
})
If you want to mock complete API class, please check the below snippet.
jest.mock('YOUR_API_PATH', () => ({
__esModule: true,
default: class {
constructor(args) {
this.var1 = args.var1
}
someMethod: jest.fn(() => Promise.resolve())
},
}));

test class method that return another class

I want to test whether the main class method of getService return the correct new class based on the correct conditional
// main.js
import ServiceA from './serviceA'
import ServiceB from './serviceB'
class Main {
constructor(){}
getService(serviceName){
switch(serviceName){
case 'serviceA':
return new ServiceA()
case 'serviceB':
return new ServiceB()
default
return null
}
}
}
Would it be possible to test that the returned class is correct? I tried something like this
import Main from './Main'
describe('Main method', () => {
describe('getService given ServiceA', () => {
it.skip('should return an instantiate of ServiceA class', function () {
const main = new Main();
const getService = spy(main, 'getService');
main.getService('serviceA');
expect(getService).to.be.an.instanceOf(ServiceA);
});
});
There shouldn't be a need to spy on getService(). Since you are just testing the input and output of the getService() without any dependencies. Spying would allow to see call count and arguments passed to the spy to see if it was called inside the method you are testing. How you had it is mostly correct as it is.
import Main from './Main'
describe('Main method', () => {
describe('getService given ServiceA', () => {
it('should return an instantiate of ServiceA class', function () {
const main = new Main();
const result = main.getService('serviceA');
expect(result).to.be.an.instanceOf(ServiceA);
});
});

How to test class instance inside a function with Jest

I have the following hypothetical scenario:
// file MyClass.js in an external package
class MyClass {
myfunc = () => {
// do something
}
}
// file in my project
function myFunctionToBeTested() {
const instance = new MyClass()
instance.myFunc()
}
I need to create a test with Jest that makes sure instance.myFunc was called
One of the option is to replace MyClass module with mock implementation
const mockmyfunc = jest.fn()
jest.mock("path/to/external/package/MyClass", () => {
return jest.fn().mockImplementation(() => {
return {myfunc: mockmyfunc}
})
})
And then write following test
it("Test myfunc called in functionToBeTested", () => {
functionToBeTested()
expect(mockmyfunc).toHaveBeenCalled()
})
Note that this is not the only way, you can dive into https://facebook.github.io/jest/docs/en/es6-class-mocks.html for other alternatives.
Update
If the myfunc would be an actual function (which i guess is not an option since it's external package?)
export class MyClass {
myFunc() {
// do smth
}
}
and you would not need to replace the implementation, you could be using jest's automock
import MyClass from "path/to/external/package/MyClass"
jest.mock("path/to/external/package/MyClass")
it("Test myfunc called in functionToBeTested", () => {
functionToBeTested()
const mockMyFunc = MyClass.mock.instances[0].myFunc
expect(mockMyFunc).toHaveBeenCalled()
})
you can mock out the class and assign the default export of that file to a variable as follows:
jest.mock('../../utils/api/api');
const FakeClass = require('../someFile.js').default;
then access calls to a function on your mock class like this:
FakeClass.prototype.myFunc.mock.calls

WebdriverIO ES6 PageObject method is not a function error

I am attempting to define a PageObject using the syntax defined in the WebdriverIO Docs:
Parent Page
//page.js
export default class Page {
constructor() {
this.title = 'My Page';
}
open(path) {
browser.url(path);
}
}
Child PageObject
// login.page.js
import Page from './page';
class LoginPage extends Page {
open() {
super.open('/login');
}
}
export default new LoginPage();
Then when I call the open method of the Login Page:
const LoginPage = require('../../pages/login.page');
LoginPage.open();
I get a TypeError:
TypeError: LoginPage.open is not a function
[chrome #0-0] at World.module.exports ...
[chrome #0-0] at Promise (<anonymous>)
[chrome #0-0] at F (/.../node_modules/core-js/library/modules/_export.js:35:28)
Work around
I can work around this problem by re-writing my PageObjects using Object.create rather than the class keyword (as described in the above linked docs).
Its not essential that I use the class keyword but I don't like to not knowing why this is failing. Please forgive me if it is obvious why this isn't working I am very new to JS.
I think you are mixing ES6 and CommonJS syntax here. Change your test to use ES6 syntax and it should start working as you expect.
import LoginPage from '../../pages/login.page';
describe('login tests', () => {
beforeAll(() => {
LoginPage.open();
});
it('should do something', () => {
//do something
});
})
I found this to work for me
http://gelionprime.ml/2017/10/page-object-pattern-webdriverio/
PARENT
//page.js
class Page {
constructor() {
this.title = 'My Page';
}
open(path) {
browser.url(path);
}
}
module.exports = Page;
CHILD
// login.page.js
import Page from './page';
class LoginPage extends Page {
open() {
super.open('/login');
}
}
module.exports = new LoginPage();
test Spec file using page object
const LoginPage = require('./login.page.js');
describe('test', () => {
it('can open', () => {
LoginPage.open();
});
});

Categories

Resources