I have a module which configures axios:
// config/axiosConfig.js
import axios from 'axios';
const instance = axios.create({
baseURL: 'http://localhost:8080/api/v1'
});
export default instance;
And a module that uses this to make api calls:
// store/actions.ts
import axiosInstance from 'config/axiosConfig';
export const fetchUsers = () => (dispatch: ThunkDispatch<{}, {}, AnyAction>) => {
dispatch(loading(true));
return axiosInstance.get('/users')
.then(res => {
dispatch(loading(false));
dispatch(fetched(res.data));
})
.catch(err => dispatch(error(true)));
}
...
I want to mock the axios config file to test my api file. I've tried many many ways but nothing works. I thought it would be as simple as
// store/actions.test.ts
import axiosInstance from 'config/axiosConfig';
jest.mock('config/axiosConfig');
axiosConfig.get.mockResolvedValue({users: mockUserList});
...
But I guess that's not how it works.
Edit: The approach in my question works when I put axiosConfig.get.mockResolvedValue({users: mockUserList}); inside of the test, not right under the mock call.
Try this out (Put this at the top of the file or at the top inside beforeAll or beforeEach depending on what you prefer):
jest.mock('config/axiosConfig', () => ({
async get(urlPath) {
return {
users: mockUserList,
};
},
}));
This is a simple mock using a factory function. To use a mock everywhere, jest provides a better way to avoid repeating yourself. You create a __mocks__ directory and inside that, you can create your module and then override many of the builtins. Then, you can get away with just the following code.
// file.test.ts
jest.mock('fs')
// Rest of your testing code below
Take a look at the official docs to learn more about this.
If this doesn't work, then the module resolution setup in jest.config.js and tsconfig.js might be different.
Related
I'm trying to temporarily mock node-fetch in an ESM module while still retraining the original implementation so I can access a real endpoint's value. However, this errors with "Must use import to load ES Module." I recognize jest support for ESM is still pending - is there any way to have this behavior in a combination of current Node, ES6, and Jest?
worker.ts (dependency):
export default async () => {
const response = await fetch("http://example2.org");
return await response.json()
}
main.test.ts:
import { jest } from "#jest/globals";
jest.mock("node-fetch", () => {
return Promise.resolve({
json: () => Promise.resolve({ myItem: "abc" }),
})
})
import doWork from './worker.js';
import mockedFetch from 'node-fetch';
const originalFetch = jest.requireActual('node-fetch') as any;
test("Ensure mock", async () => {
const result = await doWork();
expect(result.myItem).toStrictEqual("abc");
expect(mockedFetch).toBeCalledTimes(1);
const response = await originalFetch("http://www.example.org");
expect(response.status).toBe(200);
const result2 = await doWork();
expect(result2.myItem).toStrictEqual("abc");
expect(mockedFetch).toBeCalledTimes(2);
});
First, Jest doesn't support jest.mock in ESM module for tests
Please note that we currently don't support jest.mock in a clean way
in ESM, but that is something we intend to add proper support for in
the future. Follow this issue for updates.
This is reasonable because import has different semantic than require. All imports are hoisted and will be evaluated at module load before any module code.
So in your case because you are using jest.mock I assume that your test code are transformed. In this case, if you want to use non "CommonJS" package, you should transform it too. You can change transformIgnorePatterns in jest config to []. It means that all packages from node_modules will go through transform. If it's too aggressive, you can pick specific modules which ignore like this "transformIgnorePatterns": [ "node_modules/(?!(node-fetch))" ] but don't forget about transitive dependencies. ;)
Add/change in jest config
"transformIgnorePatterns": []
jest.mock accept module factory which should returns exported code. In your case, you should write it like this.
jest.mock("node-fetch", () => {
return {
__esModule: true,
default: jest.fn(() =>
Promise.resolve({
json: () => Promise.resolve({ myItem: "abc" }),
})
),
};
});
If you have error
The module factory of jest.mock() is not allowed to reference any out-of-scope variables
just remove jest from import and use it as global variable or import it inside the module factory function
https://github.com/facebook/jest/issues/2567
Change const originalFetch = jest.requireActual('node-fetch') to
const originalFetch = jest.requireActual('node-fetch').default;
E.g. I have a problem when some endpoints are down or causing errors, but I still need to develop.
I have something like UserService.getUsers I am aware of mocking functions in jest and so on, but I have no idea how I'd better get fake data which I can specify myself.
I thought about a HOC, which redefines functions I need to mock like
UserService.getUsers=Promise.resolve({});
But I don't think this is the best way to do this
NOTE: That's not related to testing stuff
If you're using Webpack, you could use the resolve property of the Webpack configuration to alias your imports to some local mocked version.
https://webpack.js.org/configuration/resolve/
For example, if you had some import like this:
import UserService from 'some-package-or-path';
const myData = UserService.getUsers();
You can alias some-package-or-path to a local file:
// webpack.config.js
const path = require('path');
module.exports = {
//...
resolve: {
alias: {
'some-package-or-path': path.resolve(__dirname, 'src/mocks/userServiceMock'),
},
},
};
// userServiceMock index.js
const mock = {
getUserData: () => {
return {
mockedDataProperties: 'whatever'
}
}
};
export default mock;
Obviously your implementation will differ a bit but that's the gist of it.
How can I mock a function that is being imported inside the file that contains the function I am testing?
without putting it in the mocks folder.
// FileIWantToTest.js
import { externalFunction } from '../../differentFolder';
export const methodIwantToTest = (x) => { externalFunction(x + 1) }
I need to make sure that externalFunction is called and its called with the correct arguments.
Seems super simple but the documentation doesn't cover how to do this without mocking the module for all the files in the test by putting the mocks in the mocks folder.
The solution: I need to take my jest.mock call outside of any test or any other function because jest needs to hoist it. Also for named exports like in my case I also have to use the following syntax:
jest.mock('../../differentFolder', () => ({
__esModule: true,
externalFunction: jest.fn(),
}));
One of the easiest approaches is to import the library and use jest.spyOn to spy on the method:
import { methodIwantToTest } from './FileIWantToTest';
import * as lib from '../../differentFolder'; // import the library containing externalFunction
test('methodIwantToTest', () => {
const spy = jest.spyOn(lib, 'externalFunction'); // spy on externalFunction
methodIwantToTest(1);
expect(spy).toHaveBeenCalledWith(2); // SUCCESS
});
I have several Redux-Thunk-style functions that dispatch other actions in one file. One of these actions dispatches the other as part of its logic. It looks similar to this:
export const functionToMock = () => async (dispatch) => {
await dispatch({ type: 'a basic action' });
};
export const functionToTest = () => async (dispatch) => {
dispatch(functionToMock());
};
In the case I'm actually running into, the functions are both much more involved and dispatch multiple action objects each. As a result, when I test my real-world functionToTest, I want to mock my real-world functionToMock. We already test functionToMock extensively, and I don't want to repeat the logic in those tests in functionToTest.
However, when I try that, like so:
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
jest.mock('../exampleActions');
const actions = require('../exampleActions');
const mockStore = configureMockStore([thunk]);
describe('example scenario showing my problem', () => {
test('functionToTest dispatches fuctionToMock', () => {
actions.functionToMock.mockReturnValue(() => Promise.resolve());
const store = mockStore({});
store.dispatch(actions.functionToTest());
expect(actions.functionToMock.mock.calls.length).toBe(1);
});
});
I get this error:
FAIL test.js
● example scenario showing my problem › functionToTest dispatches fuctionToMock
Actions must be plain objects. Use custom middleware for async actions.
at Error (native)
at dispatch (node_modules\redux-mock-store\dist\index-cjs.js:1:3137)
at Object.dispatch (node_modules\redux-thunk\lib\index.js:14:16)
at Object.<anonymous> (test.js:15:23)
(The example code I posted actually produces this error if you set them up in an environment with Jest, Redux, and Redux-Thunk. It is my MVCE.)
One thought I had is that I can move the two functions into different files. Unfortunately, doing so would break pretty dramatically with how the rest of our project is organized, so I'm not willing to do that unless it is truly the only solution.
How can I mock functionToMock in my tests for functionToTest without getting this error?
One solution is just to mock functionToMock. This question and its answers explain how to do so: How to mock imported named function in Jest when module is unmocked
This answer in particular explains that in order to get this approach to work when you're using a transpiler like Babel, you might need to refer to exports.functionToMock instead of functionToMock within functionToTest (outside of your tests), like so:
export const functionToTest = () => async (dispatch) => {
dispatch(exports.functionToMock());
};
Your example in the question helped me fix a separate bug. The solution was to toss thunk into const mockStore = configureMockStore([thunk]);. Thanks!
I'm working on a react native app right now and I wrote my own module to store some information separated from my components.
While I did that I noticed that in my module I created, I had a function like this:
testFetch = function(url){
return fetch(url);
}
exports.test = testFetch;
In my main module I did the following:
import ts from 'test-module'
ts.test("http://example.com").then((response) => {console.log(response)});
But .then() is not getting fired for any reason. The request went through and was successful.
Any help on this?
The problem is the way you mix CommonJS and ES6 modules. You seem to expect that ts in your example main module gives you the value of the whole export in your module dependency, but that's not how it works.
You can use export default to export your testFetch function, like so:
testFetch = function (url) {
return fetch(url);
}
export default testFetch;
Main module:
import testFetch from 'test-module';
testFetch("http://example.com").then((response) => {console.log(response)});