Jest - mocking non-default imported function - javascript

I need to mock an imported function in a React application (created using create-react-app) with Jest. I tried to do what is shown in the docs and in other threads (e.g. here: Jest – How to mock non-default exports from modules?), but it doesn't work. Currently my project looks like this:
calls getOne.js
import {getOne} from "../../Services/articleService";
export const a = async function () {
return getOne("article1");
};
articleService.js
export const getOne = async function (id) {
return;
};
testss.test.js
import {ArticleTypes} from "../../Data/articleTypes";
import {getOne} from "../../Services/articleService";
import {a} from "./calls getOne";
jest.mock("../../Services/articleService", () => {
return {
getOne: jest.fn().mockImplementation(async (id) => {
const articles = {
article1: {
title: "Some valid title",
description: "Some valid description",
type: /*ArticleTypes.General*/"a"
}
};
return Promise.resolve(articles[id]);
})
};
});
const mockedGetOne = async (id) => {
const articles = {
article1: {
title: "Some valid title",
description: "Some valid description",
type: ArticleTypes.General
}
};
return Promise.resolve(articles[id]);
};
beforeAll(() => {
getOne.mockImplementation(mockedGetOne);
});
test("calls getOne", async () => {
const res = await a();
expect(getOne).toHaveBeenCalled();
expect(res).not.toBeUndefined();
});
I have currently commented out ArticleTypes in the mock in the factory function used in jest.mock. I need to use them but I cannot use imported files inside the factory function. I just wanted to test if mocking would work there, but it does not work anywhere.
Why can't I mock the imports? Am I missing something? Do I need some additional configuration I don't know about?

Related

How to Mock ES6 Named Module Imports With Jest

I am trying to mock some ES6 named modules with Jest and so far no luck.
Here's my directory structure,
root
- app
controller.js
service.js
- tests
controller.test.js
babel.config.json
jest.config.mjs
package.json
yarn.lock
Here's how service.js looks like,
export const getCustomers = async () => {
const result = await new Promise( ( resolve ) => {
resolve( {
name: "Json Bourne",
age: 27,
profession: "Its a secret"
} );
} );
return result;
}
And controller.js
import { getCustomers } from "./service";
export const getAllCustomers = async () => {
const result = await getCustomers()
return { ...result }
}
And the test with the mocks
import { jest } from '#jest/globals'
import { getAllCustomers } from "../app/controller";
jest.mock( '../app/service', () => ( {
__esModule: true,
getCustomers: jest.fn().mockReturnValue( {
name: "Dummy Name",
age: 27,
profession: "Dummy Profession"
} )
} ) );
describe( 'controller tests', () => {
it( 'should correctly return customers', async () => {
const mockResult = {
name: "Dummy Name",
age: 27,
profession: "Dummy Profession"
}
const result = await getAllCustomers();
expect( result ).toBeDefined();
expect( result ).toStrictEqual( mockResult );
} );
} );
This code does not use the mock function for some reason and hits the real getCustomers. This implementation is based on the accepted answer in this similar question. I tried using the jest.spyOn on like below and that gave me an error TypeError: Cannot assign to read only property 'getCustomers' of object '[object Module]' I am using ES6 Modules. "type": "module", in `package.json.
import * as service from "../app/service";
jest.spyOn( service, "getCustomers" ).mockReturnValue( mockResult );
Any leads on how I can use mocking with ES6 named imports?
PS: I tried using default imports and it worked. i.e. the following code worked
// In service.js
const getCustomers = // Same code like above
const service = {
getCustomers
}
export default service;
And in test
import service from "../app/service";
jest.spyOn( service, "getCustomers" ).mockReturnValue( mockResult ); // This worked
But I want to use the named imports. I don't want to convert my code to use the default imports. Any help is really appreciated. Thanks in advance.

Mock uuid in jest - uuid called in service function

I have a jest test that is calling the real function and it compares the result returned with an expected result. The service function called uses uuid. I have all kind of errors while trying to mock uuid and can't seem to succeed.
My code is:
import uuid from 'uuid';
import tinyRuleset from './tiny_ruleset.json';
import { Store } from '../store';
describe('TuningStore test ', () => {
let store;
let db;
beforeEach(async () => {
db = levelup(encode(memdown(), { valueEncoding: 'json' }));
store= new Store(db);
});
test('createObject()', async () => {
jest.spyOn(uuid, 'v4').mockReturnValue('abc22');
const obj = await store.createObject();
expect(obj ).toEqual({
a: expect.any(string),
b: 'tiny_ruleset',
v: expect.any(Function)
});
});
})
I tried several ways, but none of them worked. My current error is: uuid is not a function. Also tried this:
const uuidv4Spy = jest.spyOn(store.$uuid, 'v4').mockReturnValueOnce('fake uuid');
Basically uuid is used inside the store.createObject() function.
Thank you!
As explained here Mock uuid
const uuidMock = jest.fn().mockImplementation(() => {
return 'my-none-unique-uuid';
});
jest.mock('uuid', () => {
return uuidMock;
});
you need to apply the mock in the test file before you are importing your real file.

How to mock axios.create([config]) function to return its instance methods instead of overriding them with mock?

I'm trying to mock axios.create() because I'm using its instance across the app and obviously need all of its implementation which is destroyed by the mock, thus cannot get the result of the get, post method properly.
This is how the code looks like in the actual file:
export const axiosInstance = axios.create({
headers: {
...headers
},
transformRequest: [
function (data, headers) {
return data;
},
],
});
const response = await axiosInstance.get(endpoint);
And here is the mock setup for axios inside the test file
jest.mock('axios', () => {
return {
create: jest.fn(),
get: jest.fn(() => Promise.resolve()),
};
}
);
How could I get all of the instance methods in the axiosInstance variable instead of just having a mock function which does nothing?
Documentation for axios.create and instance methods: https://github.com/axios/axios#instance-methods
You can use jest's genMockFromModule. It will generate jest.fn() for each of the modules's methods and you'll be able to use .mockReturnThis() on create to return the same instance.
example:
./src/__mocks__/axios.js
const axios = jest.genMockFromModule('axios');
axios.create.mockReturnThis();
export default axios;
working example
Edit:
from Jest 26.0.0+ it's renamed to jest.createMockFromModule
Ended up sticking with the axios mock and just pointing to that mock by assigning the axiosInstance pointer to created axios mock.
Basically as #jonrsharpe suggested
Briefly:
import * as m from 'route';
jest.mock('axios', () => {
return {
create: jest.fn(),
get: jest.fn(() => Promise.resolve()),
};
}
);
m.axInstance = axios
Would be very nice though if I could have gone without it.
Since I need to manage Axios instances, I need a way of retrieving the mocks that are created so I can manipulate the responses. Here's how I did it.
import type { AxiosInstance, AxiosStatic } from 'axios';
const mockAxios = jest.createMockFromModule<AxiosStatic>('axios') as jest.Mocked<AxiosStatic>;
let mockAxiosInstances: jest.Mocked<AxiosInstance>[] = [];
mockAxios.create = jest.fn((defaults) => {
const mockAxiosInstance = jest.createMockFromModule<AxiosInstance>(
'axios'
) as jest.Mocked<AxiosInstance>;
mockAxiosInstance.defaults = { ...mockAxiosInstance.defaults, ...defaults };
mockAxiosInstances.push(mockAxiosInstance);
return mockAxiosInstance;
});
export function getMockAxiosInstances() {
return mockAxiosInstances;
}
export function mostRecentAxiosInstanceSatisfying(fn: (a: AxiosInstance) => boolean) {
return mockAxiosInstances.filter(fn).at(-1);
}
export function clearMockAxios() {
mockAxiosInstances = [];
}
export default mockAxios;
I added three additional methods:
clearMockAxios which clears the instance list
getMockAxiosInstances which gets the list of axios instances that are generated by axios.create
mostRecentAxiosInstanceSatisfying is a function that will do a filter to get the most recent axios instance that satisfies the predicate. This is what I generally use in the test case since React may render more than expected (or as expected) and I generally just need the last instance.
I use it as follows:
import { getMockAxiosInstances, mostRecentAxiosInstanceSatisfying, clearMockAxios } from '../../__mocks__/axios';
it("...", () => {
...
const mockAppClient = mostRecentAxiosInstanceSatisfying(
a => a.defaults.auth?.username === "myusername"
);
mockAppClient.post.mockImplementation(() => {
return Promise.resolve({ data: "AAA" })
})
... do something that calls the client ...
});
The following code works!
jest.mock("axios", () => {
return {
create: jest.fn(() => axios),
post: jest.fn(() => Promise.resolve()),
};
});

Jest - mock a named class-export in typescript

I have a node module which exports a few classes, one of which is Client, which I use to create a client (having a few APIs as methods).
I'm trying to test my module which uses this node module as a dependency using Jest. However, I've been unable to successfully mock the one method (say search()) in the Client class.
Here is my spec for myModule:
//index.spec.ts
import * as nock from 'nock';
import * as externalModule from 'node-module-name';
import { createClient } from './../../src/myModule';
describe(() => {
beforeAll(() => {
nock.disableNetConnect();
});
it('test search method in my module', () => {
jest.mock('node-module-name');
const mockedClient = <jest.Mock<externalModule.Client>>externalModule.Client;
const myClient = createClient({/*params*/}); //returns instance of Client class present in node module by executing Client() constructor
myClient.searchByName('abc'); //calls search API - I need to track calls to this API
expect(mockedClient).toHaveBeenCalled();
expect(mockedClient.prototype.search).toHaveBeenCalledWith('abc');
});
});
This, however, doesn't create a mock at all and triggers a nock error since the search API tries to connect to the url (given through params).
I've also tried mocking the Client class like the following. While successfully creating a mock for the Client class and also the search API (verified that search() is also mocked through console logs), it gives me an error while I try to check if search() has been called.
externalModule.Client = jest.fn(() => { return { search: jest.fn(() => Promise.resolve('some response')) } });
//creates the mock successfully, but not sure how to track calls to 'search' property
const client = myModule.createClient(/*params*/);
client.searchByName('abc');
expect(externalModule.Client).toHaveBeenCalled(); //Successful
expect(externalModule.Client.prototype.search).toHaveBeenCalled(); //returns error saying "jest.fn() value must be a mock function or spy, Received: undefined"
I'm not sure what I'm doing wrong. Thank you in advance.
Mocking whole module
Try moving jest.mock to the top of file
//index.spec.ts
const search = jest.fn();
jest.mock('node-module-name', () => ({
Client: jest.fn(() => ({ search }))
}));
import * as nock from 'nock';
import * as externalModule from 'node-module-name';
import { createClient } from './../../src/myModule';
describe(() => {
beforeAll(() => {
nock.disableNetConnect();
});
it('test search method in my module', () => {
const myClient = createClient({/*params*/});
myClient.searchByName('abc');
expect(externalModule.Client).toHaveBeenCalled();
expect(search).toHaveBeenCalledWith('abc');
externalModule.Client.mockClear();
search.mockClear();
});
});
Mocking only Client
Create search constant and track it.
const search = jest.fn();
externalModule.Client = jest.fn(() => ({ search }));
const client = myModule.createClient(/*params*/);
client.searchByName('abc');
expect(externalModule.Client).toHaveBeenCalled();
expect(search).toHaveBeenCalled();
Here is how I mocked it. I had to change naming and removing some code to avoid exposing original source.
jest.mock('../foo-client', () => {
return { FooClient: () => ({ post: mockPost }) }
})
Full code.
// foo-client.ts
export class FooClient {
constructor(private config: any)
post() {}
}
// foo-service.ts
import { FooClient } from './foo-client'
export class FooLabelService {
private client: FooClient
constructor() {
this.client = new FooClient()
}
createPost() {
return this.client.post()
}
}
// foo-service-test.ts
import { FooService } from '../foo-service'
const mockPost = jest.fn()
jest.mock('../foo-client', () => {
return { FooClient: () => ({ post: mockPost }) }
})
describe('FooService', () => {
let fooService: FooService
beforeEach(() => {
jest.resetAllMocks()
fooService = new FooService()
})
it('something should happened', () => {
mockPost.mockResolvedValue()
fooService.createPost()
})
})

How do I test axios in Jest?

I have this action in React:
export function fetchPosts() {
const request = axios.get(`${WORDPRESS_URL}`);
return {
type: FETCH_POSTS,
payload: request
}
}
How do I test Axios in this case?
Jest has this use case on their site for asynchronous code where they use a mock function, but can I do this with Axios?
Reference: An Async Example
I have done this so far to test that it is returning the correct type:
it('should dispatch actions with the correct type', () => {
store.dispatch(fetchPosts());
let action = store.getActions();
expect(action[0].type).toBe(FETCH_POSTS);
});
How can I pass in mock data and test that it returns?
Without using any other libraries:
import * as axios from "axios";
// Mock out all top level functions, such as get, put, delete and post:
jest.mock("axios");
// ...
test("good response", () => {
axios.get.mockImplementation(() => Promise.resolve({ data: {...} }));
// ...
});
test("bad response", () => {
axios.get.mockImplementation(() => Promise.reject({ ... }));
// ...
});
It is possible to specify the response code:
axios.get.mockImplementation(() => Promise.resolve({ status: 200, data: {...} }));
It is possible to change the mock based on the parameters:
axios.get.mockImplementation((url) => {
if (url === 'www.example.com') {
return Promise.resolve({ data: {...} });
} else {
//...
}
});
Jest v23 introduced some syntactic sugar for mocking Promises:
axios.get.mockImplementation(() => Promise.resolve({ data: {...} }));
It can be simplified to
axios.get.mockResolvedValue({ data: {...} });
There is also an equivalent for rejected promises: mockRejectedValue.
Further Reading:
Jest mocking documentation
A GitHub discussion that explains about the scope of the jest.mock("axios") line.
A related question which addresses applying the techniques above to Axios request interceptors.
Using jest functions like mockImplementation in TypeScript: Typescript and Jest: Avoiding type errors on mocked functions
I used axios-mock-adapter.
In this case the service is described in ./chatbot.
In the mock adapter you specify what to return when the API endpoint is consumed.
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import chatbot from './chatbot';
describe('Chatbot', () => {
it('returns data when sendMessage is called', done => {
var mock = new MockAdapter(axios);
const data = { response: true };
mock.onGet('https://us-central1-hutoma-backend.cloudfunctions.net/chat').reply(200, data);
chatbot.sendMessage(0, 'any').then(response => {
expect(response).toEqual(data);
done();
});
});
});
You can see it the whole example here:
Service:
https://github.com/lnolazco/hutoma-test/blob/master/src/services/chatbot.js
Test:
https://github.com/lnolazco/hutoma-test/blob/master/src/services/chatbot.test.js
I could do that following the steps:
Create a folder __mocks__/ (as pointed by #Januartha comment)
Implement an axios.js mock file
Use my implemented module on test
The mock will happen automatically
Example of the mock module:
module.exports = {
get: jest.fn((url) => {
if (url === '/something') {
return Promise.resolve({
data: 'data'
});
}
}),
post: jest.fn((url) => {
if (url === '/something') {
return Promise.resolve({
data: 'data'
});
}
if (url === '/something2') {
return Promise.resolve({
data: 'data2'
});
}
}),
create: jest.fn(function () {
return this;
})
};
Look at this
The function to test album.js
const fetchAlbum = function () {
return axios
.get("https://jsonplaceholder.typicode.com/albums/2")
.then((response) => {
return response.data;
});
};
The test album.test.js
const axios = require("axios");
const { fetchAlbum } = require("../utils.js");
jest.mock("axios");
test("mock axios get function", async () => {
expect.assertions(1);
const album = {
userId: 1,
id: 2,
title: "sunt qui excepturi placeat culpa",
};
const payload = { data: album };
// Now mock axios get method
axios.get = jest.fn().mockResolvedValue(payload);
await expect(fetchAlbum()).resolves.toEqual(album);
});
I've done this with nock, like so:
import nock from 'nock'
import axios from 'axios'
import httpAdapter from 'axios/lib/adapters/http'
axios.defaults.adapter = httpAdapter
describe('foo', () => {
it('bar', () => {
nock('https://example.com:443')
.get('/example')
.reply(200, 'some payload')
// test...
})
})
For those looking to use axios-mock-adapter in place of the mockfetch example in the Redux documentation for async testing, I successfully used the following:
File actions.test.js:
describe('SignInUser', () => {
var history = {
push: function(str) {
expect(str).toEqual('/feed');
}
}
it('Dispatches authorization', () => {
let mock = new MockAdapter(axios);
mock.onPost(`${ROOT_URL}/auth/signin`, {
email: 'test#test.com',
password: 'test'
}).reply(200, {token: 'testToken' });
const expectedActions = [ { type: types.AUTH_USER } ];
const store = mockStore({ auth: [] });
return store.dispatch(actions.signInUser({
email: 'test#test.com',
password: 'test',
}, history)).then(() => {
expect(store.getActions()).toEqual(expectedActions);
});
});
In order to test a successful case for signInUser in file actions/index.js:
export const signInUser = ({ email, password }, history) => async dispatch => {
const res = await axios.post(`${ROOT_URL}/auth/signin`, { email, password })
.catch(({ response: { data } }) => {
...
});
if (res) {
dispatch({ type: AUTH_USER }); // Test verified this
localStorage.setItem('token', res.data.token); // Test mocked this
history.push('/feed'); // Test mocked this
}
}
Given that this is being done with jest, the localstorage call had to be mocked. This was in file src/setupTests.js:
const localStorageMock = {
removeItem: jest.fn(),
getItem: jest.fn(),
setItem: jest.fn(),
clear: jest.fn()
};
global.localStorage = localStorageMock;
New tools for testing have been introduced since the question was initially answered.
The problem with mocking is that you often test the mock and not the real context of your code, leaving some areas of this context untested.
An improvement over telling axios what promise to return is intercepting http requests via Service Workers.
Service worker is a client-side programmable proxy between your web app and the outside world. So instead of mocking promise resolution it is a more broader solution to mock the proxy server itself, intercepting requests to be tested. Since the interception happens on the network level, your application knows nothing about the mocking.
You can use msw (Mock Service Worker) library to do just that. Here is a short video explaining how it works.
The most basic setup I can think of is this:
1️⃣ set up handlers, which are similar to express.js routing methods;
2️⃣ set up mock server and pass handlers as it’s arguments;
3️⃣ configure tests to so that mock server will intercept our requests;
4️⃣ perform tests;
5️⃣ close mock server.
Say you want to test the following feature:
import axios from "axios";
export const fetchPosts = async () => {
const request = await axios.get("/some/endpoint/");
return {
payload: request,
};
};
Then test could look like this:
import { rest } from "msw";
import { setupServer } from "msw/node";
import fetchPosts from "./somewhere";
// handlers are usually saved in separate file(s) in one destined place of the app,
// so that you don't have to search for them when the endpoints have changed
const handlers = [ 1️⃣
rest.get("/some/endpoint/", (req, res, ctx) =>
res(ctx.json({ message: "success" }))
),
];
const server = setupServer(...handlers); 2️⃣
beforeAll(() => {
server.listen(); 3️⃣
});
describe("fetchPosts", () => {
it("should return 'success' message", async () => {
const resp = await fetchPosts();
expect(resp.payload?.data?.message).toEqual("success"); 4️⃣
});
});
afterAll(() => {
server.close(); 5️⃣
});
The configuration may be different depending on framework you are using. Some general examples for, among others, React (both REST and GraphQL) and Angular can be found on MSW’ repo. A Vue example is provided by VueMastery.
You can also find examples on MSW' recipes page.

Categories

Resources