How to mock jest.spyOn for a specific axios call - javascript

How do I mock a specific axios call?
Imagine 2 GET calls:
await axios.get('api/numbers');
await axios.get('api/letters');
Then this is going on in the test:
const mockGet = jest.spyOn(axios, 'get');
mockGet.mockReturnValueOnce(Promise.resolve({ data: 1 }));
mockGet.mockReturnValueOnce(Promise.resolve({ data: 'a' }));
How do I create a condition for mockReturnValueOnce based on the url passed to axios (say for 'api/numbers' -> return Promise.resolve({ data: 1 }))

So far, there is no method of jestjs to do this. Here is the proposal: Parameterised mock return values
Here are the solutions:
Use sinon.js
sinon.js support mock the returned value based on the arguments, there is a method named stub.withArgs(arg1[, arg2, ...]);
Use jest-when - A when(fn).calledWith(args).thenReturn(value) lib for jest
If you insist on using jestjs, since mockReturnValueOnce returns values sequentially(first call, second call, etc.). You can use the below way:
index.js:
import axios from 'axios';
export async function main() {
const numbersRes = await axios.get('api/numbers');
const lettersRes = await axios.get('api/letters');
return { numbersRes, lettersRes };
}
index.test.js:
import { main } from '.';
import axios from 'axios';
describe('59751925', () => {
it('should pass', async () => {
const mockGet = jest.spyOn(axios, 'get');
mockGet.mockImplementation((url) => {
switch (url) {
case 'api/numbers':
return Promise.resolve({ data: 1 });
case 'api/letters':
return Promise.resolve({ data: 'a' });
}
});
const actual = await main();
expect(actual.numbersRes).toEqual({ data: 1 });
expect(actual.lettersRes).toEqual({ data: 'a' });
});
});
Unit test results:
PASS src/stackoverflow/59751925/index.test.js (11.168s)
59751925
✓ should pass (9ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 13.098s

Related

jest mock function dont work in my test case

when i import modules, i mock them with jest.mock() api :
import analytics, { fakeAnalyticsApi } from "../../middleware/analytics";
jest.mock("../../middleware/analytics", () => {
const orginalModules = jest.requireActual("../../middleware/analytics");
return {
__esModule: true,
...orginalModules,
fakeAnalyticsApi: jest.fn(() => Promise.resolve("success")),
};
});
then when i call "fakeAnalyticsApi" function Its original version is executed.
full test file:
import analytics, { fakeAnalyticsApi } from "../../middleware/analytics";
jest.mock("../../middleware/analytics", () => {
const orginalModules = jest.requireActual("../../middleware/analytics");
return {
__esModule: true,
...orginalModules,
fakeAnalyticsApi: jest.fn(() => Promise.resolve("success")),
};
});
const create = () => {
const store = {
getState: jest.fn(() => {}),
dispatch: jest.fn(),
};
const next = jest.fn();
const invoke = (action) => analytics(store)(next)(action);
return { store, next, invoke };
};
// suite test
describe("analytics middleware", () => {
test("should pass on irrelevant keys", () => {
const { next, invoke } = create();
const action = { type: "IRREVELANT" };
invoke(action);
expect(next).toHaveBeenCalledWith(action);
expect(fakeAnalyticsApi).not.toHaveBeenCalled();
});
test("should make an analytics API call", () => {
const { next, invoke } = create();
const action = {
type: "REVELANT",
meta: {
analytics: {
event: "REVELANT",
data: { extra: "stuff" },
},
},
};
invoke(action);
expect(fakeAnalyticsApi).toHaveBeenCalled();
expect(next).toHaveBeenCalledWith(action);
});
});
and I get the following error:
● analytics middleware › should make an analytics API call
expect(jest.fn()).toHaveBeenCalled()
Expected number of calls: >= 1
Received number of calls: 0
39 | };
40 | invoke(action);
> 41 | expect(fakeAnalyticsApi).toHaveBeenCalled();
| ^
42 | expect(next).toHaveBeenCalledWith(action);
43 | });
44 | });
I log a statement in orginal version function of "fakeAnalyticsApi", it seems orginal version was call because the console log the statement
I found the problem
It was not related to the mock function in the jest
If an ES6 module directly exports two functions (not within a class, object, etc., just directly exports the functions like in the question) and one directly calls the other, then that call cannot be mocked.
i implemented fakeAnalyticsApi inside the analytics.js module and exported it to mock it for test purpose , but it doesn`t work.
more details: https://stackoverflow.com/a/55193363/17615078

Jasmine: Testing that a method passed in as an argument to another method gets run

My code is as follows
//agency_controller.js
import axios from 'axios';
export const getProducerNamesAndBillingPlan = ({ agencyId = '', onSuccess= (x) => x } = {} ) => {
if(!!agencyId) {
axios.get('/agency/' + agencyId)
.then(response => onSuccess.call(this, response['data']))
.catch(error => console.error(error))
}
}
//agency_controller.spec.js
import { getProducerNamesAndBillingPlan } from "../../../../app/javascript/packs/controllers/agencies_controller";
import axios from 'axios';
const mockAxiosPromise = (response) => {
return new Promise((resolve, _reject) => {
resolve({ status: 200, data: response});
});
}
describe('#getProducerNamesAndBillingPlan', () => {
...
it('calls the given onSuccess method if the request is successful', () => {
spyOn(axios, 'get').and.callFake(() => {
return mockAxiosPromise('foo')
})
const mockMethod = (x) => console.log(x)
spyOn(console.log, 'call')
getProducerNamesAndBillingPlan({ agencyId: 1, onSuccess: mockMethod })
expect(console.log.call).toHaveBeenCalledWith('foo')
})
})
I can tell that the code is working because when I run the test, 'foo' gets logged to the console. However the test still fails:
#getProducerNamesAndBillingPlan calls the given onSucess method if the request is sucessful FAILED
Expected spy call to have been called with [ 'foo' ] but it was never called.
at UserContext.<anonymous> (spec/javascripts/packs/controllers/agencies_controller.spec.js:1:17348)
Same happens with expect(console.log).toHaveBeenCalledWith('foo'). Am I doing something wrong?
The axios.get() method returns a promise, but the getProducerNamesAndBillingPlan function does not return it. You call it in the test case. When the code executes the expect statement, the promise is not resolved or rejected, so your onSuccess method was not called before the assertion.
Use async/await in test case to make sure the promise is resolved or rejected before the assertion.
agency_controller.js:
import axios from 'axios';
export const getProducerNamesAndBillingPlan = ({ agencyId = '', onSuccess = (x) => x } = {}) => {
if (!!agencyId) {
return axios
.get('/agency/' + agencyId)
.then((response) => onSuccess.call(this, response['data']))
.catch((error) => console.error(error));
}
};
agency_controller.spec.js:
import axios from 'axios';
import { getProducerNamesAndBillingPlan } from './agency_controller';
describe('#getProducerNamesAndBillingPlan', () => {
it('calls the given onSuccess method if the request is successful', async () => {
spyOn(axios, 'get').and.resolveTo({ status: 200, data: 'foo' });
const mockMethod = (x) => console.log(x);
spyOn(console, 'log');
await getProducerNamesAndBillingPlan({ agencyId: 1, onSuccess: mockMethod });
expect(console.log).toHaveBeenCalledWith('foo');
});
});
Test result:
Executing 1 defined specs...
Running in random order... (seed: 00239)
Test Suites & Specs:
1. #getProducerNamesAndBillingPlan
✔ calls the given onSuccess method if the request is successful (5ms)
>> Done!
Summary:
👊 Passed
Suites: 1 of 1
Specs: 1 of 1
Expects: 1 (0 failures)
Finished in 0.01 seconds

Mocked copyFile and stat function not getting called in jest

I am trying to mock copyFile and stat method from fs modules(fs.promises). But the mocked function is not being called and instead the original functions are called though the test cases pass.
The testing function code is:
jest.doMock('fs', () => ({
promises: {
copyFile: (src = 'source', dest = 'destination') =>
jest.fn().mockImplementation(async () => {
console.log('Inside the mock function in copyFile, please get executed, got frustrated', src, dest);
return Promise.resolve(false);
}),
stat: () =>
jest.fn().mockImplementation(async () => {
console.log('Inside the mock function in stat method, please get executed, got frustrated');
return Promise.resolve(false); // Probably wrong datatype
}),
},
}));
describe('Testing implementation', () => {
const sample = new MainFunction()
test('Testing', async () => {
expect(sample.demo()).toEqual(Promise.resolve(true));
});
});
Actual Code which needs to be tested:
import * as fs from 'fs';
export class MainFunction {
async demo(): Promise<any> {
const fileName = 'C:/Users/Desktop/testing-file-dir/';
const fileName1 = '/destination/'
let filefound = (await fs.promises.stat(fileName)).isFile();
await fs.promises.copyFile(fileName,fileName1);
console.log(filefound, 'inside actual code');
return Promise.resolve(true);
}
}
Can someone please help regarding where I am going wrong ? I had thought of using jest.mock but it was also giving error so I followed this link https://github.com/facebook/jest/issues/2567 which suggested to try doMock. If someone knows better way to handle this mock function, it would be great.
Thanks !
You can use jest.mock(moduleName, factory, options), and you didn't mock the method chain call correctly. You should use mockFn.mockReturnThis() to return this context to the caller.
E.g.
index.ts:
import * as fs from 'fs';
export class MainFunction {
async demo(): Promise<any> {
const fileName = 'C:/Users/Desktop/testing-file-dir/';
const fileName1 = '/destination/';
let filefound = (await fs.promises.stat(fileName)).isFile();
await fs.promises.copyFile(fileName, fileName1);
console.log(filefound, 'inside actual code');
return Promise.resolve(true);
}
}
index.test.ts
import { MainFunction } from './';
jest.mock('fs', () => ({
promises: {
copyFile: jest.fn().mockImplementation((src = 'source', dest = 'destination') => {
console.log('Inside the mock function in copyFile, please get executed, got frustrated', src, dest);
return Promise.resolve(false);
}),
stat: jest.fn().mockReturnThis(),
isFile: jest.fn().mockImplementation(() => {
console.log('Inside the mock function in stat method, please get executed, got frustrated');
return Promise.resolve(false);
}),
},
}));
describe('Testing implementation', () => {
const sample = new MainFunction();
test('Testing', async () => {
const actual = await sample.demo();
expect(actual).toBeTruthy();
});
});
test result:
PASS examples/66429093/index.test.ts
Testing implementation
✓ Testing (10 ms)
console.log
Inside the mock function in stat method, please get executed, got frustrated
at Object.<anonymous> (examples/66429093/index.test.ts:12:15)
console.log
Inside the mock function in copyFile, please get executed, got frustrated C:/Users/Desktop/testing-file-dir/ /destination/
at Object.<anonymous> (examples/66429093/index.test.ts:7:15)
console.log
Promise { false } inside actual code
at MainFunction.<anonymous> (examples/66429093/index.ts:9:13)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 3.12 s, estimated 4 s
Based upon slideshowp2's solution, I had to do this change in order to avoid the error as stated in this https://github.com/facebook/jest/issues/2567.
The actual file remains same while test file changes to:
jest.mock('fs', () => {
const originalModule = jest.requireActual('fs'); // so as to not override other functions apart from below mentioned one's
return Object.assign({ __esModule: true }, originalModule, {
promises: {
copyFile: jest.fn().mockImplementation((src, dest) => {
// src, dest are parameters passed in copyFile from src to destination
let source = 'some source'; // sample source file
if (source === src) {
return true;
} else {
throw Error;
}
}),
stat: jest.fn().mockReturnThis(),
isFile: jest
.fn()
.mockImplementationOnce(() => { // I had series of test so for first one I wanted false hence this part, else we can remove this and directly use .mockImplementation()
return false;
})
.mockImplementation(() => {
return true;
}),
},
});
});
describe('Testing implementation', () => {
const sample = new MainFunction();
test('Testing', async () => {
const actual = await sample.demo();
expect(actual).toBeTruthy();
});
});

Mocking a method on "this" object in jest

I have the following implementation:
export const actions = {
async submitPhoneNumber(context) {
let data = await this.$axios.
$get('https://jsonplaceholder.typicode.com/todos/1')
// do something with data
return data
}
}
When I run my test I get
TypeError: Cannot read property '$get' of undefined
How do I mock this.$axios.$get?
I looked at mocking global in jest but mocking global is just mocking window.whatever.
I need to mock this object.
This is my test:
import { actions } from '#/store/channel-add'
import flushPromises from 'flush-promises'
describe('channel-add', () => {
it('submits phone number and returns phone code hash', async () => {
let data = await actions.submitPhoneNumber()
await flushPromises()
expect(data).toBeTruthy()
})
})
Here is the solution:
index.ts:
export const actions = {
// I don't know where you get $axios from this, you didn't give the completed code. so I made a fake one for the demo.
$axios: {
$get: url => ''
},
async submitPhoneNumber(context) {
let data = await this.$axios.$get('https://jsonplaceholder.typicode.com/todos/1');
// do something with data
data = this.processData(data);
return data;
},
// for demo
processData(data) {
return data;
}
};
index.spec.ts:
import { actions } from './';
actions.$axios = {
$get: jest.fn()
};
describe('actions', () => {
it('should mock action.$axios.$get method', () => {
expect(jest.isMockFunction(actions.$axios.$get)).toBeTruthy();
});
it('should get data correctly', async () => {
(actions.$axios.$get as jest.Mock<any, any>).mockResolvedValueOnce({ userId: 1 });
const actualValue = await actions.submitPhoneNumber({});
expect(actualValue).toEqual({ userId: 1 });
expect(actions.$axios.$get).toBeCalledWith('https://jsonplaceholder.typicode.com/todos/1');
});
});
Unit tests result:
PASS src/mock-module/axios/index.spec.ts
actions
✓ should mock action.$axios.$get method (4ms)
✓ should get data correctly (4ms)
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 2.181s, estimated 3s

How do change implementation of a Jest mock for a certain test

I've create a file in my test/__mocks__ folder where I mock a npm module. And the link of this file is add to my jest setup. All work great this let me test it pretty nicely. But now for a certain test I need to change the return value from this one. How can I achieve this?
I try to unMock plus setMock etc. But nothing work.
// test/__mocks__/touchId.ts
jest.mock('react-native-touch-id', () => {
return {
isSupported: jest.fn(() => Promise.resolve(true)),
authenticate: jest.fn(() => Promise.resolve(true)),
};
});
And my test
it('should not navigate to main if touch id return false', async () => {
jest.setMock('react-native-touch-id', {
authenticate: jest.fn(() => Promise.resolve(false)),
});
const pinCreation = new PinCreationStore();
const spy = jest.spyOn(NavigationServices, 'navigate');
spy.mockReset();
await pinCreation.verifyUser();
expect(spy).toHaveBeenCalledTimes(0);
spy.mockRestore();
});
Here I still get true so my test crash.
You can use jest.mock() without creating __mocks__ folder.
For example:
react-native-touch-id.ts, I simulate this module in order to keep it simple. You can replace it with real npm module.
const touchId = {
isSupported() {
return false;
},
authenticate() {
return false;
}
};
export default touchId;
react-native-touch-id.spec.ts:
import touchId from './react-native-touch-id';
jest.mock('./react-native-touch-id', () => {
return {
isSupported: jest.fn(() => Promise.resolve(true)),
authenticate: jest.fn(() => Promise.resolve(true))
};
});
describe('react-native-touch-id', () => {
it('t1', () => {
expect(touchId.isSupported()).toBeTruthy();
expect(touchId.authenticate()).toBeTruthy();
});
it('t2', () => {
(touchId.isSupported as jest.MockedFunction<typeof touchId.isSupported>).mockReturnValueOnce(false);
(touchId.authenticate as jest.MockedFunction<typeof touchId.authenticate>).mockReturnValueOnce(false);
expect(touchId.isSupported()).toBeFalsy();
expect(touchId.authenticate()).toBeFalsy();
});
it('t3', () => {
(touchId.isSupported as jest.MockedFunction<typeof touchId.isSupported>).mockReturnValueOnce(true);
(touchId.authenticate as jest.MockedFunction<typeof touchId.authenticate>).mockReturnValueOnce(false);
expect(touchId.isSupported()).toBeTruthy();
expect(touchId.authenticate()).toBeFalsy();
});
});
As you can see, after you mock react-native-touch-id module, you need to import it and mocked again when you want these two methods to have different values. These mocked values will be used in other modules which import and use isSupported and authenticate methods of react-native-touch-id module.
Unit test result:
PASS src/stackoverflow/52172531/react-native-touch-id.spec.ts
react-native-touch-id
✓ t1 (5ms)
✓ t2 (1ms)
✓ t3
Test Suites: 1 passed, 1 total
Tests: 3 passed, 3 total
Snapshots: 0 total
Time: 4.065s

Categories