As part of my redux action, it makes several sequential api requests. The apiCall method returns a Promise with some value, and that value is used by a subsequent apiCall to make another request, and so on. I'm using Jest to test these api calls.
const myAPI = {
apiCall(param: any): Promise<any> {
return new Promise((resolve, reject) => {
resolve('result');
});
},
};
const serialAPIRequests = () => {
myAPI.apiCall('first_param')
.then((result) => {
console.log(result);
return myAPI.apiCall(result);
})
.then((result) => {
console.log(result);
return myAPI.apiCall(result);
})
.then((result) => {
console.log(result);
return Promise.resolve(result);
});
};
I am trying to write a test to ensure apiCall has been called the correct number of times and with the right parameters.
describe.only('my test', () => {
it('should test api stuff', () => {
myAPI.apiCall = jest.fn()
.mockReturnValueOnce(Promise.resolve('result1'))
.mockReturnValueOnce(Promise.resolve('result2'))
.mockReturnValueOnce(Promise.resolve('result3'));
serialAPIRequests();
expect(myAPI.apiCall).toHaveBeenCalledTimes(3);
});
});
What happens is that Jest report Expected mock function to have been called three times, but it was called one time.
Jest test result shows that
● Console
console.log
result1
console.log
result2
console.log
result3
● my test › should test api stuff
expect(jest.fn()).toHaveBeenCalledTimes(3)
Expected mock function to have been called three times, but it was called one time.
The fact that console.log showed different values means the mocked return was properly passed through the mock function and it was called 3 times.
What could be causing this and how do I properly test this function?
Use async/await to test async code. Read more here: https://facebook.github.io/jest/docs/en/tutorial-async.html
describe.only('my test', () => {
it('should test api stuff', async () => {
myAPI.apiCall = jest.fn()
.mockReturnValueOnce(Promise.resolve('result1'))
.mockReturnValueOnce(Promise.resolve('result2'))
.mockReturnValueOnce(Promise.resolve('result3'));
await serialAPIRequests();
expect(myAPI.apiCall).toHaveBeenCalledTimes(3);
});
});
Promises are async so by the time you do you check the mock was actually called once.
You could do this instead. Wait for all calls to be done and return a promise to indicate the test is async.
return serialAPIRequests().then(() => {
expect(myAPI.apiCall).toHaveBeenCalledTimes(3);
})
Related
I'm using Mocha and SuperTest to test my Express API. However my first test always seems to pass when inside the .then() of my request().
I'm passing in a String to a test that is expecting an Array. So should definitely fail the test.
It fails outside of the then() as expected, but I won't have access to the res.body there to perform my tests.
Here is my code:
const expect = require('chai').expect;
const request = require('supertest');
const router = require('../../routes/api/playlist.route');
const app = require('../../app');
describe('Playlist Route', function() {
// before((done) => {
// }
describe('Get all playlists by user', function() {
it('Should error out with "No playlists found" if there are no Playlists', function() {
request(app).get('/api/playlists/all')
.then(res => {
const { body } = res;
// Test passes if expect here
expect('sdfb').to.be.an('array');
})
.catch(err => {
console.log('err: ', err);
});
// Test fails if expect here
expect('sdfb').to.be.an('array');
})
})
});
I found this article but I'm not using a try catch block, but I thought maybe it could have something to do with the promise.
Quick reponse
it('decription', function(done) {
asyncFunc()
.then(() => {
expect(something).to.be(somethingElse)
done()
})
})
Detailed response in the comment of #jonrsharpe
Rather than using done, simply return request(app).get('/api/playlists/all') since request() returns a promise. Since you have expect('sdfb').to.be.an('array'); twice, remove the one that's not in the .then callback. When using asynchronous code, remember that synchronous code that appears to come after the async chain will execute before the promise .then handlers. This is counterintuitive.
Here's the .then approach:
it('should ...', () => {
return request(app)
.get('/api/playlists/all')
.then(res => {
const {body} = res;
// assert here
});
});
The other approach is to await the promise yourself in the test case function, then make assertions on the resolved response object. In this case, drop the then chain. This approach is generally preferred as it reduces nesting.
it('should ...', async () => {
const res = await request(app).get('/api/playlists/all');
const {body} = res;
// assert here
});
If you don't let Mocha know you're working with asynchronous code by returning a promise, awaiting the promises, or adding and calling the done parameter, the assertions occur asynchronously after the test is over and disappear into the void, creating a false positive.
Skip .catch either way. Since you've informed Mocha of the promise, if it rejects, it'll let you know.
We have a function in our React project to fetch a list of stores. If the fetch takes longer than 10 seconds we abort the request and show an error.
const controller = new AbortController();
const getTimeout = setTimeout(() => controller.abort(), 10000);
const fetchStores = storeId => (
ourFetchStoresFunction(`http://my-api/${storeId}`, {
headers: { 'x-block': 'local-stores' },
signal: controller.signal
})
.then((results) => {
clearTimeout(getTimeout);
return results
})
.catch((err) => { throw err; })
);
Here is the basic test for the fetchStores function:
it('fetchStores should return a stores array', () => {
storesAPI.fetchStores(MOCK_STORES)
.then((stores) => {
expect(Array.isArray(stores)).toBe(true);
})
.catch();
});
How do I mock this timeout in Jest?
using setTimeout in the .then block while calling that method did not work. Additionally, I would rather NOT have to wait 10 seconds during the test. I looked up jest.useFakeTimers but got nowhere.
I could test global timers, read here: jestjs.io/docs/timer-mocks You can use jest spyOn function. For example:
let timeoutSpy;
beforeEach(() => {
timeoutSpy = jest.spyOn(global, 'setTimeout');
});
afterEach(() => {
jest.restoreAllMocks();
})
Then in your test, you can expect(tymepoutSpy).toHaveBeenCalledTimes(1); Something like this should work, worked in my case.
In your case, you can mock about controller too, again using jest.spyOn, could be something like this:
let abortSpy;
In the same beforeEach you can set it:
abortSpy = jest.spyOn(AbortController.prototype, 'abort');
And I suppose ourFetchStoresFunction function is similar to fetch?
And finally, you can mock fetch function, so it takes lots f time to load, it can be done in this way:
> global.fetch = jest.fn(() => Promise.resolve({ // set some time })
> );
I have a module:
// foo.js
module.exports = async () => {
...
}
This module is called in another module, which behaviour I'm testing:
// bar.js
const one = await foo();
const two = await foo();
I want to mock foo with Jest, so that multiple calls on it return different results. More precisely, the first call to be successful, the second one to return an error.
This is my mocking mechanism:
const fooMock = jest.mock('../src/foo')
fooMock
.mockImplementationOnce(() => Promise.resolve({ id: 'asdf' }))
.mockImplementationOnce(() => Promise.reject(new Error('some error')))
The problem is that mockImplementationOnce is not a function of jest.mock(). It's only a function of jest.fn(). The jest.mock() object only has mockImplementation which will mock and seal the return result of the mocked function and doesn't allow for different results on multiple calls.
How can I mock the module to return different results on 1st and on 2nd call?
Inspiration taken from the jest docs here.
UPDATE:
I also tried this approach:
jest.mock('../src/foo', () => jest.fn()
.mockImplementationOnce(() => Promise.resolve({ _id: 'asdf' }))
.mockImplementationOnce(() => Promise.reject('some error'))
)
But now no mocking is happening at all.
You should use mockFn.mockReturnValueOnce(value):
Accepts a value that will be returned for one call to the mock function. Can be chained so that successive calls to the mock function return different values
After calling jest.mock('./src/foo'), you should import the ./src/foo module and it will be a mocked version instead of using the return value.
const fooMock = require('./src/foo');
jest.mock('./src/foo');
test('should pass', () => {
fooMock.mockReturnValue('default')
.mockReturnValueOnce('first call')
.mockReturnValueOnce('second call')
// 'first call', 'second call', 'default', 'default'
console.log(fooMock(), fooMock(), fooMock(), fooMock());
})
I am trying to test a queuing component that makes calls and handles a lot of scheduling. I want to test it with a mock api where the api responses are delayed as they would be in real life, but I want to use mock timers and fake the passage of time. In the following bare-bones example, the object under test is the Caller object.
function mockCall(): Promise<string> {
return new Promise<string>(resolve => setTimeout(() => resolve("success"), 20));
}
const callReceiver = jest.fn((result: string) => { console.log(result)});
class Caller {
constructor(call: () => Promise<string>,
receiver: (result: string) => void) {
call().then(receiver);
}
}
it("advances mock timers correctly", () => {
jest.useFakeTimers();
new Caller(mockCall, callReceiver);
jest.advanceTimersByTime(50);
expect(callReceiver).toHaveBeenCalled();
});
I would think this test should pass, but instead the expect is evaluated before the timer is advanced, so the test fails. How can I write this test so it will pass?
By the way, this test does pass if I use real timers and delay the expect for more than 20 milliseconds, but I am specifically interested in using fake timers and advancing time with code, not waiting for real time to elapse.
The reason is mockCall still returns Promise, even after you mocked timer. So call().then() will be executed as next microtask. To advance execution you can wrap your expect in microtask too:
it("advances mock timers correctly", () => {
jest.useFakeTimers();
new Caller(mockCall, callReceiver);
jest.advanceTimersByTime(50);
return Promise.resolve().then(() => {
expect(callReceiver).toHaveBeenCalled()
});
});
Beware of returning this Promise so jest would wait until it's done. To me using async/await it would look even better:
it("advances mock timers correctly", async () => {
jest.useFakeTimers();
new Caller(mockCall, callReceiver);
jest.advanceTimersByTime(50);
await Promise.resolve();
expect(callReceiver).toHaveBeenCalled();
});
Btw the same thing each time you mock something that is returning Promise(e.g. fetch) - you will need to advance microtasks queue as well as you do with fake timers.
More on microtasks/macrotasks queue: https://abc.danch.me/microtasks-macrotasks-more-on-the-event-loop-881557d7af6f
Jest repo has open proposal on handling pending Promises in more clear way https://github.com/facebook/jest/issues/2157 but no ETA so far.
You can make the test work by returning the promise to jest as otherwise the execution of your test method is already finished and does not wait for the promise to be fulfilled.
function mockCall() {
return new Promise(resolve => setTimeout(() => resolve('success'), 20));
}
const callReceiver = jest.fn((result) => { console.log(result); });
class Caller {
constructor(callee, receiver) {
this.callee = callee;
this.receiver = receiver;
}
execute() {
return this.callee().then(this.receiver);
}
}
describe('my test suite', () => {
it('advances mock timers correctly', () => {
jest.useFakeTimers();
const caller = new Caller(mockCall, callReceiver);
const promise = caller.execute();
jest.advanceTimersByTime(50);
return promise.then(() => {
expect(callReceiver).toHaveBeenCalled();
});
});
});
I have the following test in my react native application, but the test should fail (because the action returned is not equal to the action I put in expectedActions. My guess is that it is passing because the expect test runs after the test has completed.
How can I force the test to wait until the promise is completed and the expect test runs? Is there another way of doing this?
describe('authorize actions', () => {
beforeEach(() => {
store = mockStore({});
});
it('should create an action to signify successful auth', () => {
auth.authorize.mockImplementation(() => Promise.resolve({"something": "value"}));
const expectedActions = [{"type":"AUTHORIZE_RESPONSE","payload":{"something":"sdklfjsdf"}}];
authorizeUser(store.dispatch, store.state).then(() => {
expect(store.getActions()).toEqual(expectedActions);
});
})
});
Ok, looks like I just missed some of the Jest docs - if you return the promise, i.e. return auth.authorize.mockImplementation(() => Promise.resolve... then Jest will wait until it's completed before continuing.
The are varios ways to test async code. Check the docs for examples: https://facebook.github.io/jest/docs/en/asynchronous.html
One could be returning the promise:
describe('authorize actions', () => {
beforeEach(() => {
store = mockStore({});
});
it('should create an action to signify successful auth', () => {
auth.authorize.mockImplementation(() => Promise.resolve({"something": "value"}));
const expectedActions = [{"type":"AUTHORIZE_RESPONSE","payload":{"something":"sdklfjsdf"}}];
return authorizeUser(store.dispatch, store.state).then(() => {
expect(store.getActions()).toEqual(expectedActions);
});
})
});