JS/JestJS: How to test for a async mocked function? - javascript

As you can see in my testfile I've mocked all mongoDB methods.
Now I need to test, if Content.update() has been called. In this example code I'm testing for a called console.log which is working, but not want I want.
I don't understand why I can't test for update()
/category.test.js
import { updateCategory } from './category'
import DB from './lib/db'
test('should update document', async () => {
DB.getDB = jest.fn(
() => ({
get: jest.fn(
() => ({
findOne: jest.fn(() => ({ some: 'content' })),
update: jest.fn()
})
)
})
)
console.log = jest.fn()
return updateCategory({}, {
id: '12345678901234567'
}).then(() => {
expect(console.log).toHaveBeenCalled()
// instead check if `Content.update()` has been called
expect(DB.getDB().get().update).toHaveBeenCalled() // throws `Expected mock function to have been called.`
})
})
/category.js
import DB from './lib/db'
export async function updateCategory (obj, { id }) {
const db = DB.getDB()
const Content = db.get('content')
const doc = await Content.findOne({ _id: id })
console.log('ok');
await Content.update(
{ _id: id },
{ $set: { category: 'new category' } }
)
}

Store the mock you want to spy on in a variable so you can track it:
import { updateCategory } from './category'
import DB from './lib/db'
jest.mock('./lib/db');
const mockUpdate = jest.fn();
test('should update document', async () => {
DB.getDB.mockImplementation( // <-- available when you call jest.mock above, seems safer than overwriting the implementation in the real import
() => ({
get: jest.fn(
() => ({
findOne: jest.fn(() => ({ some: 'content' })),
update: mockUpdate
})
)
})
)
console.log = jest.fn()
return updateCategory({}, {
id: '12345678901234567'
}).then(() => {
expect(console.log).toHaveBeenCalled()
// instead check if `Content.update()` has been called
expect(mockUpdate).toHaveBeenCalled() // should work now
})
})

Related

get new Instance for function each test

I have some tests in one file,
I check my reducer with some case
My code looks like this
my code
import axiosInstance from '~/utils/network';
const fetcher = axiosInstance();
const fetchMiddleware = () => {
switch (type) {
case 'LOGOUT':{
try {
await fetcher.get(API.GET.LOGOUT_OPERATION);
dispatch({ type: 'LOGOUT_SUCCESS' });
} catch (err) {
dispatch({ type: 'LOGOUT_FAIL' });
}
});
}
}
}
my test
import axiosInstance from '../../src/utils/network';
import configureStore from 'redux-mock-store';
const middlewares = [fetchMiddleware, thunk];
const mockStore = configureStore(middlewares);
const store = mockStore(getInitialReducerState());
jest.mock('../../src/utils/network', () => {
const axiosInstance = jest.fn().mockImplementation(() => {
return {
get: jest.fn().mockImplementation(() => {
return {
headers: {},
};
}),
};
}) as any;
axiosInstance.configure = jest.fn();
return axiosInstance;
});
describe('test LOGOUT', () => {
beforeEach(() => {
store.clearActions();
});
it('should test be success', async () => {
await store.dispatch({
type: 'LOGOUT',
payload: { userName: 'testUserName' },
});
expect(store.getActions()).toContainEqual({
type: 'LOGOUT_SUCCESS',
});
});
it('should test be fail', async () => {
(axiosInstance as jest.Mock).mockImplementation(() => {
return {
get: jest.fn().mockImplementation(() => {
throw new Error(' ');
}),
};
});
await store.dispatch({
type: 'LOGOUT',
payload: { userName: 'testUserName' },
});
expect(store.getActions()).toContainEqual({
type: 'LOGOUT_FAIL',
});
});
});
I want to test two scenarios: success & fail,
I mock the axiosInstance function.
But even I override the mock in the second test I get the first mock because my code loads axiosInstance only once.
what can I do?
You need to use jest.isolateModules
Let's say we have 2 files:
./lib.js - this is your ~/utils/network
./repro.js - this is your file with the code under test
./lib.js:
export default function lib() {
return () => 10;
}
./repro.js:
import lib from './lib';
const fnInstance = lib();
export const fn = () => {
return fnInstance();
};
And the ./repro.test.js:
function getRepro(libMock) {
let repro;
// Must use isolateModules because we need to require a new module everytime
jest.isolateModules(() => {
jest.mock('./lib', () => {
return {
default: libMock,
};
});
repro = require('./repro');
});
// If for some reason in the future the behavior will change and this assertion will fail
// We can do a workaround by returning a Promise and the `resolve` callback will be called with the Component in the `isolateModules` function
// Or we can also put the whole test function inside the `isolateModules` (less preferred)
expect(repro).toBeDefined();
return repro;
}
describe('', () => {
it('should return 1', () => {
const { fn } = getRepro(function lib() {
return () => 1
});
expect(fn()).toEqual(1);
});
it('should return 2', () => {
const { fn } = getRepro(function lib() {
return () => 2
});
expect(fn()).toEqual(2);
});
});
It's preferable to use existing library to mock Axios, it saves from boilerplate code and potential mistakes in mock implementation; moxios has been already suggested.
It's inconvenient to mock axiosInstance per test because it has been already called on the import of tested module, so this requires it to be re-imported per test; another answer explains how it's done with jest.isolateModules.
Since axiosInstance is evaluated only once and is supposed to return mocked object, it's convenient to mock it once per test and then change implementations:
jest.mock('~/utils/network', () => {
const axiosMock = { get: jest.fn(), ... };
return {
axiosInstance: () => axiosMock;
};
});
const axiosMock = axiosInstance();
...
(axiosMock.get axiosInstance as jest.Mock).mockImplementation(() => {
throw new Error(' ');
});
await store.dispatch(...);
This requires to use jest.restoreAllMocks in beforeEach or similar Jest configuration option to avoid test cross-contamination.
Notice that Axios doesn't throw errors but rather return rejected promises, this may affect test results, see the note regarding the benefits of libraries.

Jest tests affects other tests

I am using Jest with vuejs 2.
When I run the test separetly It succeed, but when I run tests in the whole file It fails.
I tried all solutions proposed to clearMocks and resetMocks ect, but It didn't work with me.
The project is available on github.
Here is my Code:
// views/analytics.ts
import { getAnalytics } from '#/client/analytics'; // this method returns a Promise
export default Vue.extend({
...
data() {
return {
property1: '',
}
},
mounted() {
getAnalytics(this.agentName, size).then((response) => {
this.property1 = response.propertyName
})
}
})
// client/analytics.ts
const getAnalytics = async (agentName: string, size: number): Promise<any> => {
const response = await axios.get(url, opt);
_.reverse((response as any).analytics);
return response;
};
export {
getAnalytics,
};
// analytics.spec.ts
jest.mock('#/client/analytics', () => ({
getAnalytics: () => Promise.resolve(mockedData), // mockedData is a JSON object
}));
describe ('analytics.vue', () => {
beforeEach(async () => {
jest.resetModules();
jest.clearAllMocks();
const localVue = createLocalVue();
localVue.use(ElementUI);
wrapper = shallowMount(Analytics, {
localVue,
mocks: {
$route,
},
});
comp = wrapper.vm;
});
afterEach(() => {
wrapper.destroy();
});
it('test 1', async () => {
const myProperty = comp.$data.property1;
expect(myProperty).toEqual('expectedValue');
})
it('test 2', async () => {
// same body as test 1.
})
})
I tried different propositions from here and here but It doesn't work with me.

Jest - Test catch block in an asyncThunk (Redux toolkit)

I've been struggling recently because I'm trying to fix some tests that another developer made before he left the company I'm working on. It involves testing a catch block inside a createAsyncThunk, the thunk was created as follows:
export const onEmailSubmit = createAsyncThunk(
'email/onEmailSubmit',
async (data, { dispatch, rejectWithValue, getState }) => {
dispatch(updateEmail({ isLoadingEmailRequest: true }))
try {
const response = await updateIdentity(data)
return response.data
} catch (err) {
// If the error doesn't have any status code, there is a Network Error
// so let's show the error modal:
if (err.request) {
dispatch(updateErrorModal({ showErrorModal: true }))
}
return rejectWithValue(err.data)
}
})
The test file is something along the lines:
import userReducer, { updateEmail, handleEmailVerify } from './EmailSlice'
import configureStore from 'redux-mock-store' // ES6 modules
import thunk from 'redux-thunk'
import { updateIdentity } from '../../../http'
const middlewares = [thunk]
const mockStore = configureStore(middlewares)
jest.mock('../../../http/', () => ({
...jest.requireActual('../../../http/'),
updateIdentity: jest.fn().mockImplementation((data) => {
return Promise.resolve({
simpleFieldsValues: {
displayName: 'test',
language: 'en_US'
}
})
})
}))
describe('EmailSlice unit tests', () => {
beforeEach(() => {
jest.clearAllMocks()
Object.defineProperty(window, 'sessionStorage', {
value: {
getItem: jest.fn(() => null),
setItem: jest.fn(() => null),
removeItem: jest.fn(() => null)
},
writable: true
})
})
afterEach(() => {
jest.restoreAllMocks()
})
// ...Some tests before this one
it('tests when handleEmailVerify is called and backend does not return a response', async () => {
const error = new Error()
error.request = {
message: 'test'
}
sendVerificationCode.mockImplementation(_ => Promise.reject(error))
const store = mockStore({})
await store.dispatch(handleEmailVerify())
const actions = store.getActions()
const rejectedActionLoading = actions[1]
expect(rejectedActionLoading.type).toEqual(updateEmail.type)
expect(rejectedActionLoading.payload).toEqual({ isLoadingEmailRequest: true })
const rejectedAction = actions[2]
expect(rejectedAction.type).toEqual("errorModal/updateErrorModal")
})
})
The thing is that the tests fail because the dispatch to update the error modal is never reached. I think this is related to the fact that the mocked promise sendVerificationCode.mockImplementation(_ => Promise.reject(error)) does not return the object with the request object inside of it to the catch block, thus not dispatching the updateErrorModal.
PS: If I remove the if statement and just dispatch the updateErrorModal, the test passes.
Do you guys have any idea how to fix this?
Thanks for your time :)

How to check if a method is called in nested jest mock functions

I have a mock service like below
const firebaseService = jest.fn(() => ({
initializeApp: jest.fn(() => { /*do nothing*/}),
}))
in my test I want to expect if initializeApp has been called.
How can I check that?
it('should be called', () => {
expect(???).toHaveBeenCalledTimes(1);
});
Update : Real scenario
const collection = jest.fn(() => {
return {
doc: jest.fn(() => {
return {
collection: collection,
update: jest.fn(() => Promise.resolve(true)),
onSnapshot: jest.fn(() => Promise.resolve(true)),
get: jest.fn(() => Promise.resolve(true))
}
}),
where: jest.fn(() => {
return {
get: jest.fn(() => Promise.resolve(true)),
onSnapshot: jest.fn(() => Promise.resolve(true)),
limit: jest.fn(() => {
return {
onSnapshot: jest.fn(() => Promise.resolve(true)),
get: jest.fn(() => Promise.resolve(true)),
}
}),
}
}),
limit: jest.fn(() => {
return {
onSnapshot: jest.fn(() => Promise.resolve(true)),
get: jest.fn(() => Promise.resolve(true)),
}
})
}
});
const Firestore = {
collection: collection
}
firebaseService = {
initializeApp() {
// do nothing
},
firestore: Firestore
};
and I want to check below
expect(firebaseService.firestore.collection).toHaveBeenCalled();
expect(firebaseService.firestore.collection.where).toHaveBeenCalled();
expect(firebaseService.firestore.collection.where).toHaveBeenCalledWith(`assignedNumbers.123`, '==', true);
You can define the inner spy as a variable.
const initializeAppSpy = jest.fn(() => { /*do nothing*/});
const firebaseService = jest.fn(() => ({
initializeApp: initializeAppSpy,
}))
Then you can use the reference in order to expect on it:
it('should be called', () => {
expect(initializeAppSpy).toHaveBeenCalledTimes(1);
});
EDIT
You can create a mock to the entire service
const firebaseMock = {
method1: 'returnValue1',
method2: 'returnValue2'
}
Object.keys(firebaseMock).forEach(key => {
firebaseMock[key] = jest.fn().mockReturnValue(firebaseMock[key]);
});
const firebaseService = jest.fn(() => firebaseMock);
now, you will have a firebaseMock object that all the methods are mocked.
you can expect on each one of those methods.

Jest/enzyme: How to test for .then and .catch of an nested asynchronous function

I'm trying to do some enzyme/jest unit testing for a asynchronous function in my reactJS component, which gets injected as prop.
My Problem is to test for a value in the then() part of the function and to test for catch() if an error occures.
This is how the function of my component (<CreateAccount />) looks like:
_onSubmit = (event) => {
event.preventDefault()
const { username, password } = this.state
this.props.createUserMutation({
variables: { username, password }
}).then(response => {
const token = response.data.createUser.token
if (token) {
Cookies.set('auth-token', token, { expires: 1 })
}
}).catch(error => {
console.warn(error)
})
}
The first test should check for .catch(error => {}) as data is undefined:
it('_onSubmit() should throw error if data is missing', () => {
const createUserMutation = () => {
return Promise.resolve({})
}
const wrapper = shallow(<CreateAccount createUserMutation={createUserMutation} />)
wrapper.update().find(Form).simulate('submit', {
preventDefault: () => {}
})
const state = wrapper.instance().state
expect(wrapper).toThrow() // <-- How to do it correctly?
})
And the second test should check if cookie is set correctly. Here I don't know how to do that? I think I have to mock Cookie
it('_onSubmit() should get token', () => {
const createUserMutation = () => {
return Promise.resolve({
data: {
createUser: { token: 'token' }
}
})
}
const wrapper = shallow(<CreateAccount createUserMutation={createUserMutation} />)
wrapper.find(Form).simulate('submit', {
preventDefault: () => {}
})
// How to check for token value and Cookies?
})
What I usually have to do when I want to see if the spy worked on the catch or then, is to add another then() on the test. For example:
it('_onSubmit() should throw error if data is missing', () => {
const createUserMutation = jest.fn(() => Promise.reject(new Error()));
const spy = jest.spyOn(console,"warn");
const wrapper = shallow(<CreateAccount createUserMutation={createUserMutation} />)
wrapper.update().find(Form).simulate('submit', {
preventDefault: () => {}
});
return createUserMutation.catch(() => {
expect(wrapper).toMatchSnapshot();
})
.then(() => {
expect(spy).toHaveBeenCalledTimes(1);
});
})
I guess it is somehow related to how NodeJS handles the queues, promises, ticks, etc, internally.
That is the rejected/catch branch. If you want to test the IF path, just use a Promise.resolve and return a promise.then() instead of catch.
Why are you using console.warn for an error? Use console.error instead. You will need to mock it out to a spy as well to test it.
First test:
it('_onSubmit() should throw error if data is missing', (done) => {
const createUserMutation = () => new Promise();
const wrapper = shallow(<CreateAccount createUserMutation={createUserMutation} />)
wrapper.update().find(Form).simulate('submit', {
preventDefault: () => {}
})
const state = wrapper.instance().state
createUserMutation.resolve().then(() => {
expect(console.warn).toHaveBeenCalled();
done();
});
})
If you are running this in a mock browser environment and not a real browser then you must mock out Cookies.set.
Second test:
it('_onSubmit() should get token', (done) => {
const createUserMutation = () => new Promise();
const wrapper = shallow(<CreateAccount createUserMutation={createUserMutation} />)
wrapper.find(Form).simulate('submit', {
preventDefault: () => {}
});
jest.spyOn(window.Cookies, 'set');
const response = {
data: {
createUser: { token: 'token' }
}
}
createUserMutation.resolve(response).then(() => {
expect(window.Cookies.set).toHaveBeenCalled();
done();
});
})
afterEach(() => {
// Reset the spies so that they don't leak to other tests
jest.restoreAllMocks();
});

Categories