Jest check when async function gets called - javascript

I'm trying to test whether an async function (fire and forget) gets called.
Content.js
export async function fireAndForgetFunction() {
...
}
export async function getData() {
...
fireAndForgetFunction()
return true;
}
I would like to test if fireAndForgetFunction has been called more than once.
Current test
import * as ContentFetch from '../Content';
const { getData } = ContentFetch;
const { fireAndForgetFunction } = ContentFetch;
it('test',async () => {
const spy = jest.spyOn(ContentFetch, 'fireAndForgetFunction');
await getData();
expect(spy).toHaveBeenCalled();
})
The test result to an error saying
Expected number of calls: >= 1
Received number of calls: 0
How could I do this test?

If you don't want to await for fireAndForgetFunction in getData(), which I assume is the case, then providing a mock implementation of fireAndForgetFunction when creating the spy is your best option:
it('test', (done) => {
const spy = jest.spyOn(ContentFetch, 'fireAndForgetFunction')
.mockImplementation(() => {
expect(spy).toHaveBeenCalled();
done();
})
getData();
})

Related

How to test an imported function returning a Promise in Jest?

I have this very simple function, and I must write a test. The goal is to fulfill the coverage threshold.
import { lambdaPromise } from '#helpers';
export const main = async event => lambdaPromise(event, findUsers);
The lambdaPromise() function returns a Promise. I am trying to mock it, then tell if it was called. Here's what I have:
import { main, findUsers } from '../src/lambdas/user/findUsers';
import { lambdaPromise } from '#helpers';
const mockEvent = {
arguments: {
userDataQuery: {
email: 'johndoe#whatever.com'
}
}
};
const mockLambdaPromise = jest.fn();
jest.mock('#helpers', () => ({
lambdaPromise: jest.fn().mockImplementation(() => mockLambdaPromise)
}));
describe('findUsers', () => {
it('should have a main function', async () => {
const mockPromise = main(mockEvent);
expect(mockPromise).toBeInstanceOf(Promise);
expect(mockLambdaPromise).toBeCalledWith(mockEvent, findUsers);
});
});
Now mockLambdaPromise never gets called. How to fix that?
Your mock returns a function, but you didn't call that function. The following makes it pass.
jest.mock("./helpers", () => ({
lambdaPromise: jest
.fn()
.mockImplementation((a, b) => mockLambdaPromise(a, b)),
}));
The complexity of that mock can be reduced by just mocking the resolved value with a spy:
import { main, findUsers } from "./findUsers";
import * as helpers from "./helpers";
describe("findUsers", () => {
it("should have a main function", async () => {
const spy = jest.spyOn(helpers, "lambdaPromise").mockResolvedValue();
await main(mockEvent);
expect(spy).toBeCalledWith(mockEvent, findUsers);
});
});

Node: how to test multiple events generated by an async process

I need to test an async Node.js library which periodically generates events (through EventEmitter) until done. Specifically I need to test data object passed to these events.
The following is an example using mocha + chai:
require('mocha');
const { expect } = require('chai');
const { AsyncLib } = require('async-lib');
describe('Test suite', () => {
const onDataHandler = (data) => {
expect(data.foo).to.exist;
expect(data.bar).to.exist;
expect(data.bar.length).to.be.greaterThan(0);
};
it('test 1', async () => {
const asyncLib = new AsyncLib();
asyncLib.on('event', onDataHandler); // This handler should be called/tested multiple times
await asyncLib.start(); // Will generate several 'events' until done
await asyncLib.close();
});
});
The problem is that even in case of an AssertionError, mocha marks the test as passed and the program terminates with exit code 0 (instead of 1 as I expected).
The following uses done callback instead of async syntax, but the result is the same:
require('mocha');
const { expect } = require('chai');
const { AsyncLib } = require('async-lib');
describe('Test suite', () => {
const onDataHandler = (data) => {
expect(data.foo).to.exist;
expect(data.bar).to.exist;
expect(data.bar.length).to.be.greaterThan(0);
};
it('test 1', (done) => {
const asyncLib = new AsyncLib();
asyncLib.on('event', onDataHandler);
asyncLib.start()
.then(asyncLib.close)
.then(() => done());
});
});
I have also tried with a "pure" Node.js approach using the native assert.ok without any 3rd part library:
const { strict: assert } = require('assert');
const { AsyncLib } = require('async-lib');
const test = async () => {
const onDataHandler = (data) => {
assert.ok(data.foo != null);
assert.ok(data.bar != null);
assert.ok(data.bar.length > 0);
};
asyncLib.on('event', onDataHandler);
const asyncLib = new AsyncLib();
await asyncLib.start();
await asyncLib.close();
}
(async () => {
await test();
})();
Even in this case, an AssertionError would make the program to terminate with exit code 0 instead of 1.
How can I properly test this code and make the tests correctly fail in case of an assertion error?
There are some things that you need to fix to make it works:
Make your test async, because the test is going to execute the expects after a certain event is received meaning it's going to be asyncronous.
Your event handler in this case onDataHandler should receive the done callback because there is the way how you can indicate to mocha that the test was finished successful as long as the expects don't fail.
I wrote some code and tested it out and it works, you have to make some changes to adapt your async library though:
describe('Test suite', function () {
const onDataHandler = (data, done) => {
expect(data.foo).to.exist;
expect(data.bar).to.exist;
expect(data.bar.length).to.be.greaterThan(0);
done();
};
it('test 1', async function (done) {
eventEmitter.on('event', (data) => onDataHandler(data, done));
setTimeout(() =>{
eventEmitter.emit('event', {
})
}, 400)
});
});

Testing async componentDidMount() with react-test-renderer

I have a component which does some SQLite loads in it's componentDidMount() function
async componentDidMount() {
try {
const user = await this.userDao.getUserData();
const setupNeeded = user === null;
if( setupNeeded ) {
this.setState({
status : 'setup'
});
}
else {
this.setState({
status : 'ready',
seed: user.seed
})
}
} catch (e) {
logger.error("Error during db query", e);
this.setState({
status: 'corrupted'
});
}
}
And I would like to test the rendered result after the call to getUserData() has been resolved, and the state has been set accordingly.
Now in my test, I have the actual call to the database mocked away, so the Promise should be resolved immediately, yet testing like this does not work as expected:
Initially, I tried it like this:
test('Should render SetNewPasswordScreen', async () => {
const tree = await renderer.create(<IndexScreen/>);
const json = tree.toJSON();
// expect stuff
});
However the json in this case contains the data of the initial render() call.`
Doing it like this will work:
test('Should render SetNewPasswordScreen', (done) => {
const tree = renderer.create(<IndexScreen/>);
setTimeout(() => {
const json = tree.toJSON();
// expect stuff
}, 5000);
});
But this is not ideal, because I am just guessing that after 5 seconds everything will be done, but I don't know. Also, it's less then suitable if a test takes 5 seconds to finish. (I used 5 seconds arbitrarily, probably it will also work with much less since the async call is mocked anyway, but I can never really know)
My question is if anybody has a better idea of how to solve this issue?
it('should render setNewPassWordScreen', async () => {
const tree = await renderer.create(<IndexScreen/>);
const instance = tree.getInstance();
await instance.componentDidMount();
// expect other stuff.
});
Little late for the party but faced the issues as well. Here is a handy shortcut function I use with the adviced usage of act:
import renderer from "react-test-renderer"
import { ReactElement } from "react"
import { act } from "#testing-library/react-native"
export const expectToMatchSnapshot = async (component: ReactElement) => {
let tree
await act(async () => {
tree = renderer.create(component)
})
expect(tree.toJSON()).toMatchSnapshot()
}
In your case you should only then add:
test("should render ***", async () => {
await expectToMatchSnapshot(<IndexScreen/>)
}
you could use the wait for expect package:
https://www.npmjs.com/package/wait-for-expect
test('Should render SetNewPasswordScreen', async (done) => {
const tree = renderer.create(<IndexScreen/>);
await waitForExpect(() => {
const json = tree.toJSON();
// expect stuff
});
});
mind the function passed into the test('...', fn) is now an async function.

How to test async function with spyOn?

I am trying to test an async function in a react native app.
class myClass extends React.Component {
...
closeModal = async () => {
if (someCondition) {
await myFunction1();
} else {
await myFunction2();
}
this.props.navigation.state.params.onGoBack();
this.props.navigation.navigate('Main');
};
...
}
This is my test:
const navigation = {
navigate: jest.fn(),
state: { params: { onGoBack: jest.fn() } },
};
const renderComponent = overrides => {
props = {
navigation,
...overrides,
};
return shallow(< myClass.wrappedComponent {...props} />);
};
describe('When the user presses the close icon', () => {
it('should close the modal', () => {
const wrapper = renderComponent();
const instance = wrapper.instance();
const spyCloseModal = jest.spyOn(instance, 'closeModal');
instance().forceUpdate();
component
.find({ testID: 'close-icon' })
.props()
.onPress();
expect(spyCloseModal).toHaveBeenCalled(); // this is passed
expect(navigation.navigate).toHaveBeenCalled(); // this is not passed
});
});
It looks like it gets stuck on the await calls. If I remove the await calls then it passes. Someone mentioned in another post to use .and.callThrough after spyOn but it gives me this error
Cannot read property 'callThrough' of undefined
one of solution is to make your test async and run await (anything) to split your test into several microtasks:
it('should close the modal', async () => {
const wrapper = renderComponent();
component
.find({ testID: 'close-icon' })
.props()
.onPress();
await Promise.resolve();
expect(navigation.state.params.onGoBack).toHaveBeenCalled();
expect(navigation.navigate).toHaveBeenCalledWith("Main");
});
I believe you don't need either .forceUpdate nor .spyOn on instance method. once navigation happens properly it does not matter by what internal method it has been called
more on microtask vs macrotask: https://abc.danch.me/microtasks-macrotasks-more-on-the-event-loop-881557d7af6f
alternative is to use macrotask(setTimeout(...., 0))
it('should close the modal', (done) => {
const wrapper = renderComponent();
component
.find({ testID: 'close-icon' })
.props()
.onPress();
setTimeout(() => {
expect(navigation.state.params.onGoBack).toHaveBeenCalled();
expect(navigation.navigate).toHaveBeenCalledWith("Main");
done();
});
}
Yes, you're on the right track...the issue is that closeModal is asynchronous.
The await hasn't finished by the time execution returns to the test so this.props.navigation.navigate hasn't been called yet.
The test needs to wait for closeModal to complete before asserting that navigate has been called.
closeModal is an async function so it will return a Promise...
...and you can use the spy to retrieve the Promise it returns...
...then you can call await on that Promise in your test to make sure closeModal has completed before asserting that navigate has been called.
Here is a simplified working example to get you started:
import * as React from 'react';
import { shallow } from 'enzyme';
class MyClass extends React.Component {
closeModal = async () => {
await Promise.resolve();
this.props.navigation.navigate('Main');
}
render() { return <div onClick={() => this.closeModal()}></div> }
}
test('MyClass', async () => { // <= async test function
const props = { navigation: { navigate: jest.fn() }};
const wrapper = shallow(<MyClass {...props} />);
const instance = wrapper.instance();
const spyCloseModal = jest.spyOn(instance, 'closeModal');
wrapper.find('div').simulate('click');
expect(spyCloseModal).toHaveBeenCalled(); // Success!
const promise = spyCloseModal.mock.results[0].value; // <= get the Promise returned by closeModal
await promise; // <= await the Promise
expect(props.navigation.navigate).toHaveBeenCalled(); // Success!
})
Note the use of mockFn.mock.results to get the Promise returned by closeModal.

Why my stub Sinon.Stub is ignored?

I'm wondering why my stub is ignored.
Let say I have a file called myfile.ts which exports two async methods. A and B.
import { sendResult } from '../index'
export async A(): Promise<string[]> {
// making an async api call
const result = await apiCall()
// do sync treatement on result then send it back
...
return value
}
export async B(): Promise<void> {
// Calling A()
const aResult = await A()
// do some sync treatement and call a method from an other module than
// returns a Promise<void>
...
sendResult()
}
I need to unit test my B method and stub A and sendResult
My testFile.ts looks like
import { sandbox } from 'sinon'
import * as AB from './modules/ab'
import * as INDX from './index'
const testSandbox = sandbox.create()
describe('my tests', function () {
beforeEach(() => {
testSandbox.stub(INDX, 'sendResult').resolves()
testSandbox.stub(AB, 'A').resolves([])
})
afterEach(() => {
testSandbox.restore()
})
it('should pass but it is not', async function (done) {
await AB.B()
const sendResultStub = UL.sendResult as SinonStub
assert.calledOnce(sendResultStub)
done()
})
})
I do not understand why sendResult is well stubed but A is not. What do I miss?
Thanks is advances folks!
Bastien

Categories

Resources