How to Mock ES6 Named Module Imports With Jest - javascript

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.

Related

Jest - mocking non-default imported function

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?

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.

ES6 imports and 'is not a constructor' in Jest.mock

Similar to Jest TypeError: is not a constructor in Jest.mock, except I am using ES6 imports - and the answer given to that question is not working on my situation.
Following the Jest .mock() documentation I am attempting to mock the constructor Client from the pg module.
I have a constructor, Client, imported from an ES6 module called pg. Instances of Client should have a query method.
import { Client } from "pg";
new Client({ connectionString: 'postgresql://postgres:postgres#localhost:5432/database' });
export async function doThing(client): Promise<string[]> {
var first = await client.query('wooo')
var second = await client.query('wooo')
return [first, second]
}
Here's my __tests__/test.ts
const log = console.log.bind(console)
jest.mock("pg", () => {
return {
query: jest
.fn()
.mockReturnValueOnce('one')
.mockReturnValueOnce('two'),
};
});
import { Client } from "pg";
import { doThing } from "../index";
it("works", async () => {
let client = new Client({});
var result = await doThing(client);
expect(result).toBe(['one', 'two'])
});
This is similar to the answer given in Jest TypeError: is not a constructor in Jest.mock, but it's failing here.
The code, just:
const mockDbClient = new Client({ connectionString: env.DATABASE_URL });
fails with:
TypeError: pg_1.Client is not a constructor
I note the docs mention __esModule: true is required when using default exports, but Client is not a default export from pg (I've checked).
How can I make the constructor work properly?
Some additional notes after getting an answer
Here's a slightly longer-form version of the answer, with comments about what's happening on each line - I hope people reading this find it useful!
jest.mock("pg", () => {
// Return the fake constructor function we are importing
return {
Client: jest.fn().mockImplementation(() => {
// The consturctor function returns various fake methods
return {
query: jest.fn()
.mockReturnValueOnce(firstResponse)
.mockReturnValueOnce(secondResponse),
connect: jest.fn()
}
})
}
})
When you mock the module, it needs to have the same shape as the actual module. Change:
jest.mock("pg", () => {
return {
query: jest
.fn()
.mockReturnValueOnce('one')
.mockReturnValueOnce('two'),
};
});
...to:
jest.mock("pg", () => ({
Client: jest.fn().mockImplementation(() => ({
query: jest.fn()
.mockReturnValueOnce('one')
.mockReturnValueOnce('two')
}))
}));

how to unit test components in vue.js that rely on external dependencies?

I am trying to write a unit test (with jest) that asserts that data is being pushed into job_execs. The two issues I'm running into are mocking the api endpoint and if it's even possible to mock this.$route.params.tool in a unit test.
So the main question is, can I write a test with the component below? I know that it should be possible to mock the api endpoint (still haven't figured out how to), but my concern is that I have too many external dependencies in the component for this to be unit-testable. Do I need to rewrite my component to support unit tests?
Jobs.vue
<script>
export default {
name: "Jobs",
data() {
return {
job_execs: []
}
},
created() {
this.JobExecEndpoint = process.env.VUE_APP_UATU_URL + '/api/v1/job_execution/?tool='+this.$route.params.tool+'&job='+this.$route.params.job+'&id='+this.$route.params.id
fetch(this.JobExecEndpoint)
.then(response => response.json())
.then(body => {
this.job_execs.push({
'code_checkouts': body[0].code_checkouts,
})
})
.catch( err => {
console.log('Error Fetching:', this.JobExecEndpoint, err);
return { 'failure': this.JobExecEndpoint, 'reason': err };
})
},
};
</script>
unit test
import { shallowMount } from "#vue/test-utils";
import fetchMock from 'fetch-mock'
import flushPromises from 'flush-promises'
import Jobs from "../../src/components/execution_details/Jobs.vue";
const job_execs = [
{
'code_checkouts': [{'name': 'test', 'git_hash': 'test', 'git_repo': 'test'}, {'name': 'test', 'git_hash': 'test', 'git_repo': 'test'}]}]
const $route = {
params = {
tool: 'jenkins',
job: 'jobname',
id: '1',
}
}
describe('Jobs.vue', () => {
beforeEach(() => {
fetchMock.get(process.env.VUE_APP_UATU_URL + '/api/v2/job_execution/?product=eBay%20Mobile&tool='+$route.params.tool+'&job='+$route.params.job+'&id='+$route.params.id, job_execs)
})
it('Construct a JSON object of Git Refs from job_execution API', async () => {
const wrapper = shallowMount(GitRefs)
await flushPromises()
expect(wrapper.vm.job_execs).toEqual(job_execs)
})
afterEach(() => {
fetchMock.restore()
})
})
You need to import all the dependencies used in the component and also need to import whatever global values or properties used router, axios etc

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