Mocha test execution moves to cleanup function too soon - javascript

I have a function wrapper in alertTest.js for calling it (OOP required by the architecture team):
alertTest.js:
import { BaseTest } from "../base/BaseTest.js";
import { MainPage } from "../pages/MainPage.js";
import { Driver } from "../driver/Driver.js";
import { assert } from "chai";
export class AlertTest extends BaseTest {
run() {
console.log('here 1');
it("Alerts", async () => {
console.log('here 2');
const mainPage = new MainPage();
assert.isTrue(await mainPage.isPageOpen(), "Main page is did not open");
});
}
}
When I call run from main.js, execution moves out of it call to the BaseTest.afterEach function too soon (I think this is because I'm using async/await.
main.js:
import { AlertTest } from "./alertTest.js";
import { BaseTest } from "../base/BaseTest.js";
BaseTest.beforeEach(); // logs "in before"
const alertTest = new AlertTest();
alertTest.run();
BaseTest.afterEach(); // logs "in after"
The order of console logs is the following:
in before
here 1
in after
here 2 (at this point driver instance is cleaned up and code fails)
How can I make sure pre/post conditions and the tests run sequentially? What is the problem here?
I tried wrapping the run in a promise and using await alertTest.run() but Mocha didn't recognize any functions to run ("passed 0" in output). Not sure if this is a correct approach.
run() {
return new Promise(resolve => {
it("Alerts", async () => {
const mainPage = new MainPage();
assert.isTrue(await mainPage.isPageOpen(), "Main page is did not open");
await mainPage.clickDivCard();
resolve()
});
})
}
Changed snippet in main.js:
await alertTest.run(); // <---
BaseTest.afterEach();
I saw this question https://stackoverflow.com/questions/31281328 but still not sure how to apply fixes to my code.

Related

Mock only works for test file, but not when executing instance

I am trying to mock a method used by the class I'm testing. The method is located in the same file as the class I'm testing.
The tested file:
export function trackErrors() {
console.log("This SHOULD NOT BE SEEN ON tests");
// Currently this console log is visible hwne running tests
}
// Component that is tested
export const form = () => {
return <FormComponent callback={trackErrors} />
}
The test itself:
With mock
The jest.mock() is the first thing to happen, before the import.
jest.mock('./testedFile', () => {
const actualModule = jest.requireActual('./testedFile')
return new Proxy(actualModule, {
get: (target, property) => {
switch (property) {
case 'trackErrors': {
return jest.fn()
}
default: {
return target[property]
}
}
},
})
})
import * as SModule from "./testedFile";
it.only("Calls trackErrors", async () => {
const { getByTestId } = <SModule.form />;
await typeEmail(getByTestId, "abcd");
await waitFor(() => {
expect(SModule.trackErrors).toHaveBeenCalledTimes(1);
});
});
This does not work. When printing SModule.trackErrors, I can see the mocked function being put out. But when the test is executed, the test fails with 0 calls and I see this console.log This SHOULD NOT BE SEEN ON tests. That means that the method is mocked good inside the test, but when rendering the component it continues to call the prexistent method (the one not modked).
I've also tried the spyOn approach, but the outcome is exactly the same.

Testing Imported Function with Parameter using Jest Mock Function / Jest spyOn

I'm trying to write a unit test for a Node.js project's logic using Jest.
However, most documentations only provide a case for importing a module or class, however, in my case, my module only contains functions.
So far I know that there are mainly three ways to test a function in Jest:
1) jest.fn()
2) jest.spyOn
3) jest.mock('path')
I have tried all three but none work.
I wanted to test if the function returns a correct value(string) when called.
I tried many different
Here's my code: (I will show short snippets of my code in the later parts)
getDefApCode.ts
export function getDefApCode(code: string) {
switch (code) {
case 'BKK':
return 'NRT'
case 'CTX':
return 'ICN'
case 'SIN':
return 'TPE'
default:
return code
}
}
export function getDefaultDepartureCode(code: string) {
return code ? getDefaultLocationCode(code) : 'LHR'
}
export function getDefaultDestinationCode(code: string) {
return code ? getDefaultLocationCode(code) : 'ZRH'
}
getDefAPCode.spec.ts >> Pattern 1 (using required + jest.fn)
import { Connection, getConnection, getConnectionOptions } from "typeorm";
import { bootstrap, dbConnection } from "../../../src/app";
import { TourSearchParamsFactory } from "../../helpers/typeOrmFactory";
import * as getDefAPCode from "../../../src/controllers/logic/getDefAPCode";
describe("Logic Test", () => {
beforeAll(async () => {
await dbConnection(15, 3000);
});
afterAll(async () => {
const conn = getConnection();
await conn.close();
});
it("should get a default location code", async () => {
const getLocation = require('../../../src/controllers/logic/getDefAPCode');
const code = jest.fn(code => 'BKK');
const getCode = getLocation(code);
expect(getCode).toHaveBeenCalled();
});
});
Error Message:
TypeError: getLocation is not a function
getDefAPCode.spec.ts >> Pattern 2 (using spyON)
import { Connection, getConnection, getConnectionOptions } from "typeorm";
import { bootstrap, dbConnection } from "../../../src/app";
import { TourSearchParamsFactory } from "../../helpers/typeOrmFactory";
import * as getDefaultLocationCode from "../../../src/controllers/logic/getDefaultLocationCode";
describe("Logic Test", () => {
beforeAll(async () => {
await dbConnection(15, 3000);
});
afterAll(async () => {
const conn = getConnection();
await conn.close();
});
const { getDefaultLocationCode, getDefaultDepartureCode, getDefaultDestinationCode } = require('../../../src/controllers/logic/getDefaultLocationCode');
it("should get a default location code", async () => {
const spy = jest.spyOn(getDefaultLocationCode, 'getDefaultLocationCode');
getDefaultLocationCode.getDefaultLocationCode('AKJ');
expect(spy).toHaveBeenCalled();
});
});
These are some error messages appear when I tried a different pattern (I didn't keep track of all of the test code pattern, will add the test code pattern once I fixed docker)
Error Message:
Cannot spy the getDefaultLocationCode property because it is not a function; undefined given instead
31 | const spy = jest.spyOn(getDefaultLocationCode, 'getDefaultLocationCode');
Past Error Messages
error TS2349: This expression is not callable.
Type 'typeof import("/app/src/controllers/logic/getDefAPCode")' has no call signatures.
another one
expect(received).toHaveBeenCalled()
Matcher error: received value must be a mock or spy function
Received has type: string
Received has value: "NRT"
I figured out that I don't have to use mock function in this case.
I stored argument in a variable and then I use the variable instead using a string directly.
Here's how I edit my test code
it("should get a default location code", () => {
const code = 'BKK';
expect(code).toHaveBeenCalled();
});

Method in service is undefined when executing but not when logging to console

I have 2 Service classes
UserClass
import axios from "axios";
export default class {
constructor(){
this.http= axios.create({baseURL:"/api/users/"});
}
getUser(userId){
return this.http(userId);
}
}
BusinessClass
import axios from "axios";
import AppState from "../utils/appState";
export default class {
constructor(){
this.http= axios.create({baseURL:"/api/business/"});
this.appState = new AppState();
}
async getAllBusiness(){
try{
let result =await this.http("all");
this.appState.save('all_business', result.data);
}catch(ex){
console.log("AllBusiness",ex);
}
return;
}
}
When I import and create an instance of these in my vue component first one has all it's methods. But the second one loses it's methods in code.
When I put a debug point and log it, it will get logged as method. But when I execute it, it will log an error.
//before export default
import UserService from "../Services/UserService";
import BusinessService from "../Services/BusinessService";
//inside export default
async created(){
this.$UserService = new UserService();
this.$BusinessService = new BusinessService();
let result = await this.$UserService.getUser(this.id); //=> this one works
await this.$BusinessService.getAllBusiness(); //=>this one logs this.$BusinessService.getAllBusiness is not a function
}
I also tried these two ways to define the method
getAllBusiness(){
return new Promise((resolve,reject)=>{
this.http("all")
.then((result)=>{
this.appState.save('all_business', result.data);
resolve()
});
.catch(()=>{reject()});
});//also tried with bind(this)
}
getAllBusiness=()=>{
return new Promise((resolve,reject)=>{
this.http("all")
.then((result)=>{
this.appState.save('all_business', result.data);
resolve()
});
.catch(()=>{reject()});
});//also tried with bind(this)
}
Using console.log(this.$BusinessService.getAllBusiness) in debug will show ƒ getAllBusiness() { ... the content of the code.
But hovering on it while debugging in chrome it will show undefined
It seems the issue is with this line
.then((result)=>{
this.appState.save('all_business', result.data);
resolve()
});
In this you are not returning. Try by returning result from the then.
Also await may need to be inside async function

How to unit test promise rejection in React and Jest

I am trying to write a unit test for a react component. It's a fairly standard component which calls a promise-returning method and uses 'then' and 'catch' to handle the resolution. My test is trying to validate that it calls the correct method when the promise is rejected however despite following what i believe is a standard patttern, I cannot get jest to validate the call. I have listed the relevant files here and have also put up a github sample, which is linked at the bottom of the question. That sample is simply a new react app created using npx and the files below added in.
Here is my example component:
import React from 'react';
import api from '../api/ListApi';
class ListComponent extends React.Component {
constructor(props) {
super(props);
this.fetchListSuccess = this.fetchListSuccess.bind(this);
this.fetchListFailed = this.fetchListFailed.bind(this);
}
fetchList() {
api.getList()
.then(this.fetchListSuccess)
.catch(this.fetchListFailed);
}
fetchListSuccess(response) {
console.log({response});
};
fetchListFailed(error) {
console.log({error});
};
render() {
return(<div>Some content</div>);
};
}
export default ListComponent;
Here is the api class (note, the api doesnt exist if you run the app, its just here for example):
const getList = () => fetch("http://someApiWhichDoesNotExist/GetList");
export default { getList };
And here is the test case:
import ListComponent from './ListComponent';
import api from '../api//ListApi';
describe('ListComponent > fetchList() > When the call to getList fails', () => {
it('Should call fetchListFailed with the error', async () => {
expect.hasAssertions();
//Arrange
const error = { message: "some error" };
const errorResponse = () => Promise.reject(error);
const componentInstance = new ListComponent();
api.getList = jest.fn(() => errorResponse());
componentInstance.fetchListFailed = jest.fn(() => { });
//Act
componentInstance.fetchList();
//Assert
try {
await errorResponse;
} catch (er) {
expect(componentInstance.fetchListFailed).toHaveBeenCalledWith(error);
}
});
});
The problem is that the test is not executing the catch block, so, in this case, the expect.hasAssertions() is failing the test. Can anyone help me understand the catch block is not executing? Wrapping the await in the try block and asserting in the catch seems to be a standard pattern in the docs but I am fairly new to Js and React and am obviously doing something wrong.
Here is the sample project on GitHub. Any help would be greatly appreciated =)
In your console:
const errorResponse = () => Promise.reject();
await errorResponse;
//() => Promise.reject()
You're awaiting a function, not the result of the call to that function. You want to:
await errorResponse();
EDIT:
In addition to that, the rest of your test is confusing. I believe you actually want to test what happens when the fetchList method of your component is called, and it fails, I assume. So you need to call it in your test, and await it's response:
Update your component's fetchList method to return the promise.
await componentInstance.fetchList() instead of await errorResponse()
Because you catch the error in fetchList you'll never enter the catch or the try...catch so your final test should look like this:
Test:
//Arrange
const error = { message: "some error" };
const errorResponse = () => Promise.reject(error);
const componentInstance = new ListComponent();
api.getList = jest.fn(() => errorResponse());
componentInstance.fetchListFailed = jest.fn(() => { });
//Act
await componentInstance.fetchList();
expect(componentInstance.fetchListFailed).toHaveBeenCalledWith(error);
Component:
fetchList() {
return api.getList()
.then(this.fetchListSuccess)
.catch(this.fetchListFailed);
}
My two cents, with my own case in a React native app:
In my component I have:
const handleRedirect = React.useCallback(() => {
client.clearStore()
.then(navigation.push('SomeScreen'))
.catch(e => logger.error('error', e));
}, []);
My test is this one, where I want to test if the promise is rejected:
it('should throw an exception on clearStore rejected', async () => {
const client = {
clearStore: jest.fn()
};
const error = new Error('clearStore failed');
client.clearStore.mockReturnValue(Promise.reject(error));
expect.assertions(1);
await expect(client.clearStore())
.rejects.toEqual(Error('clearStore failed'));
});

Cannot stub functions inside a module when using sinon and babel-plugin-rewire

In a mocha/chai setup, I'm trying to use babel-plugin-rewire in conjunction with sinon for testing and stubbing functions within the same module. These are the example files below:
Firstly, an index.js and test file that uses both sinon and babel-plugin-rewire. Rewiring works, but for some reason, my stubs don't work. The function it is applied to is never stubbed and only the original value is returned:
// index.js
function foo() {
return "foo";
}
export function bar() {
return foo();
}
export function jar() {
return "jar";
}
//index.test.js
import chai from "chai";
import sinon from "sinon";
import * as index from "./index";
const expect = chai.expect;
const sandbox = sinon.sandbox.create();
describe("babel-plugin-rewire", () => {
it("should be able to rewire", () => {
index.default.__set__("foo", () => {
return "rewired"; // successfullly rewires
});
expect(index.bar()).to.equal("rewired"); // works fine
index.default.__ResetDependency__("foo");
expect(index.bar()).to.equal("bar"); // works fine
});
});
describe("sinon", () => {
afterEach(() => {
sandbox.restore();
});
it("should call the original jar", () => {
expect(index.jar()).to.equal("jar"); // works fine
});
it("should call the stubbed jar", () => {
sandbox.stub(index, "jar").returns("stub");
expect(index.jar()).to.equal("stub"); // fails
});
});
And here is two example files solely using sinon stubs. The same thing happens:
// stub.js
export function stub() {
return "stub me";
}
// stub.test.js
import * as stub from "./stub";
import sinon from "sinon";
import chai from "chai";
const expect = chai.expect;
const sandbox = sinon.createSandbox();
const text = "I have been stubbed";
describe("sinon stubs", () => {
afterEach(() => {
sandbox.restore();
});
it("should stub", () => {
sandbox.stub(stub, "stub").returns(text);
expect(stub.stub()).to.equal(text); // fails
});
});
And this here is the babelrc that is being used for mocha
{
"presets": [
"#babel/preset-env"
],
"plugins": [
"rewire"
]
}
If I remove rewire from the plugins, the problem goes away. Though obviously this means I can't use rewire, which as I mentioned earlier I need in order to stub functions within the same dependency. Is this a bug with the module or am I missing something here?
I had the same problem with one of my tests. There was an action that after signing out the user, triggered a page redirect. The redirect itself was implemented in separate file.
import * as utils from '../utils.js';
import sinon from 'sinon';
it('will dispatch an action and redirect the user when singing out', () => {
const redirectStub = sinon.stub(utils, 'redirect').returns(true);
// dispatch and assertion of action omitted
expect(redirectStub.calledOnce).toBeTruthy();
redirectStub.restore();
});
Then, for various reasons, I had to add babel-rewire-plugin to my tests. This, broke that specific test above. The calledOnce or any other sinon methods were always returning false.
Unfortunately, I wasn't able to make the spying/stubbing work anymore. I had to rewrite my test like this:
import { __RewireAPI__ } from '../utils.js';
import sinon from 'sinon';
it('will dispatch an action and redirect the user when singing out', () => {
__RewireAPI__.__Rewire__('redirect', () => true);
// dispatch and assertion of action omitted
__RewireAPI__.ResetDependencty('redirect');
});
As you can see, there are no spies or stubs assertions anymore. I rewired the redirect method and then reset it.
There is a babel-plugin-rewire-exports library however, that seems to allow you to both rewire and spy/stub. I haven't tried it myself, but it can be an option if you don't want to rewrite your tests. Here is the link.

Categories