Mock imported function in Jest - javascript

I have a case:
test.js
import { today } from "utils/date";
import myFunction from "helpers/myFunction";
it('should work properly', () => {
jest.mock('utils/date', () => ({
...(jest.requireActual('utils/date')),
today: jest.fn(() => '01-01-2020'),
}));
console.log(today()); // still logs current date 14-10-2021, not the mocked date
expect(myFunction()).toEqual(today());
});
myFunction.js
import { today } from "utils/date";
export const myFunction = () => today();
today is a function that returns todays date. But for testing purposes I need that function to always return the same date, e.g. "01-01-2020".
Note: as you can see that "today" function is used in the test as well as inside the tested (myFunction) function, so it has to return the same mocked value like everywhere in the app.
Thanks

jest.mock() is invoked in the test case functional scope. Module imports are hoisted (internally moved to the beginning of the current scope). The original today function is imported before jest.mock() mocks the utils/date module.
You can move the jest.mock() from test case functional scope to module scope. Jest will automatically hoist jest.mock calls to the top of the module (before any imports). So that when you import the today function, it is already being mocked.
See Using with ES module imports:
If you're using ES module imports then you'll normally be inclined to put your import statements at the top of the test file. But often you need to instruct Jest to use a mock before modules use it. For this reason, Jest will automatically hoist jest.mock calls to the top of the module (before any imports).
import { today } from 'utils/date';
jest.mock('utils/date', () => ({
today: jest.fn(() => '01-01-2020'),
}));
it('should work properly', () => {
expect(jest.isMockFunction(today)).toBeTruthy();
expect(today()).toBe('01-01-2020');
});

Related

Spying On/Mocking Import of an Import

I'm writing unit tests using vitest on a VueJS application.
As part of our application, we have a collection of API wrapper services, e.g. users.js which wraps our relevant API calls to retrieve user information:
import client from './client'
const getUsers = () => {
return client.get(...)
}
export default {
getUsers
}
Each of these services utilise a common client.js which in turn uses axios to do the REST calls & interceptor management.
For our units tests, I want to check that the relevant url is called, so want to spy on, or mock, client.
I have followed various examples and posts, but struggling to work out how I mock an import (client) of an import (users.js).
The closest I've been able to get (based on these posts - 1, 2) is:
import { expect, vi } from 'vitest'
import * as client from '<path/to/client.js>'
import UsersAPI from '<path/to/users.js>'
describe('Users API', () => {
beforeEach(() => {
const spy = vi.spyOn(client, 'default') // mock a named export
expect(spy).toHaveBeenCalled() // client is called at the top of users.js
})
test('Users API.getUsers', () => {
UsersAPI.getUsers()
expect(spy).toHaveBeenCalled()
})
})
but it's tripping on:
❯ async frontend/src/api/client.js:3:31
2| import store from '#/store'
3|
4| const client = axios.create({
| ^
5| headers: {
6| 'Content-Type': 'application/json'
where it's still trying to load the real client.js file.
I can't seem to mock client explicitly because the import statements run first, and so client is imported inside users.js before I can modify/intercept it. My attempt at the mocking was as follows (placed between the imports and the describe):
vi.mock('client', () => {
return {
default: {
get: vi.fn()
}
}
})
Mocking a module
vi.mock()'s path argument needs to resolve to the same file that the module under test is using. If users.js imports <root>/src/client.js, vi.mock()'s path argument needs to match:
// users.js
import client from './client' // => resolves to path/to/client.js
// users.spec.js
vi.mock('../../client.js') // => resolves to path/to/client.js
It often helps to use path aliases here.
Spying/mocking a function
To spy on or mock a function of the mocked module, do the following in test():
Dynamically import the module, which gets the mocked module.
Mock the function off of the mocked module reference, optionally returning a mock value. Since client.get() returns axios.get(), which returns a Promise, it makes sense to use mockResolvedValue() to mock the returned data.
// users.spec.js
import { describe, test, expect, vi } from 'vitest'
import UsersAPI from '#/users.js'
vi.mock('#/client')
describe('Users API', () => {
test('Users API.getUsers', async () => {
1️⃣
const client = await import('#/client')
2️⃣
const response = { data: [{ id: 1, name: 'john doe' }] }
client.default.get = vi.fn().mockResolvedValue(response)
const users = await UsersAPI.getUsers()
expect(client.default.get).toHaveBeenCalled()
expect(users).toEqual(response)
})
})
demo
Late to the party but just in case anyone else is facing the same issue.
I solved it by importing the module dependency in the test file and mocking the whole module first, then just the methods I needed.
import { client } from 'client';
vi.mock('client', () => {
const client = vi.fn();
client.get = vi.fn();
return { client }
});
Then in those tests calling client.get() behind the scenes as a dependency, just add
client.get.mockResolvedValue({fakeResponse: []});
and the mocked function will be called instead of the real implementation.
If you are using a default export, look at the vitest docs since you need to provide a default key.
If mocking a module with a default export, you'll need to provide a default key within the returned factory function object. This is an ES modules specific caveat, therefore jest documentation may differ as jest uses commonJS modules.
I've accepted the above answer, as that did address my initial question, but also wanted to include this additional step I required.
In my use case, I need to mock an entire module import, as I had a cascading set of imports on API files that in turn, imported more and more dependencies themselves.
To cut this, I found this in the vuex documentation about mocking actions:
https://vuex.vuejs.org/guide/testing.html#testing-actions
which details the use of webpack and inject-loader to substitute an entire module with a mock, preventing the source file loading at all.

Mocking environment and/or exported constants in Jest for Node apps

Setup
I have a module in an express app that I am trying to test that uses constants exported from a module. Many of these constants are derived from environment variables.
// src/constants.js
export default {
ID_SOURCE: process.env.ID_SOURCE,
ID_PROVIDER: process.env.ID_PROVIDER
};
/*
process.env {
ID_SOURCE: 'foo',
ID_PROVIDER: 'bar'
}
*/
Here is the pattern to source these constants in the module under test.
import constants from './constants';
import { errors } from './errors';
const { ID_SOURCE, ID_PROVIDER } = constants;
export const moduleUnderTest = async (req, res) => {
if (!ID_SOURCE || !ID_PROVIDER) {
if (!ID_SOURCE) logService('ID_SOURCE not provided');
if (!ID_PROVIDER) logService('ID_PROVIDER not provided');
throw { err: errors.unexpectedError };
};
// ... do stuff
return res.status(200).json({ cool: 'beans' });
}
I want to test the exception scenario where the environment variables are not present. This has proved difficult under the current pattern in the module, because even if I mock the environment, the module is being evaluated -- and the ID_SOURCE and ID_PROVIDER constants in the module are initialized -- before the environment changes.
Importantly, I only want to change the constants for ONE TEST. Here's what I've tried.
The test
import constants from './constants';
import { moduleUnderTest } from './moduleUnderTest';
describe('Test module', () => {
beforeEach(() => {
jest.clearAllMocks();
})
test('test exception scenario', async () => {
jest.doMock('./constants', () => {
const actualConstants = jest.requireActual('./constants');
return {
__esModule: true,
...actualConstants,
ID_SOURCE: undefined,
ID_PROVIDER: undefined
}
});
// ... assume req/res are available
try {
await moduleUndertest(req, res);
} catch(ex) {
// by the time we get here, the variables are set from the environment still
// because the module was evaluated and ID_SOURCE and ID_PROVIDER variables in the closure set
// before mocking the constants
expect(ex.message).toBeDefined();
}
});
Mocking the environment will not work either for the reason stated in the comments above... the module is evaluated at import and those variables are initialized from the environment before the mock changes them.
Behavior
ID_SOURCE and ID_PROVIDER always have the values assigned to them from the environment.
Of course one obvious solution is just to access the variables out of the constants in the function definition, which will only execute at their invocation... but the problem is that in the codebase I am working in this pattern is EVERYWHERE. I can't just start arbitrarily refactoring everything to make this work, and I feel that there has to be a better way.
Other things I've tried
I've also tried mocked-env, which fails for the same reasons as stated above. I've tried calling jest.resetModules in a beforeEach hook and requiring in the module inside each test, but this pattern hasn't produced any results so far. I would be interested to hear more about this pattern if this is the way.
I thought that an approach with jest setupFiles might be the solution, as I use them in other cases where an environment variable needs to be available locally (everywhere) that isn't there, but I don't want these mocked globally for every test... only one test.
What is the right pattern for doing this?
In my Jest configuration, I add a file that has my defined environment for the tests.
setupFiles: [
'<rootDir>/src/.env.testing.js',
],
In this file, I then set the environment I use:
process.env.PORT='9999';
I then have my normal services read the configuration from the environment as needed. You can use any file name for the setupFiles, I was replicating my use of the .env file.
Ultimately, this is what I did to fix it.
test('should process exception', async () => {
jest.resetModules(); // clear require.cache
jest.doMock('../src/constants', () => {
const constants = jest.requireActual('../src/constants');
console.log(constants);
return {
__esModule: true,
default: {
...constants,
ID_SOURCE: 'foo'
}
}
});
const req = makeReq({ session: { email: "foo#bar.com" }});
const res = makeRes();
const { updateUserProfile } = await import(
"../src/controller"
);
await updateUserProfile(req, res);
expect(res.status).toHaveBeenCalledWith(500);
})
One of the big issues was making sure that the require cache was clear before mocking the constants file. Then, it was simply a matter of dynamically importing the module under test after I had mocked the constants module.

Automatic mock of an ES6 class fails (Jest, Vanilla JavaScript)

I have the class FooStorage that has an Array of Foo objects as a member variable. When testing FooStorage I want to mock the class Foo.
As described at ES6 Class Mocks, an automatic mock is all I need in my case. But when I try to mock the class, it doesn't seem to succeed. Instead when I try to reset the mock with mockClear(), I recieve an error message.
Below is the code and the output from jest:
foo.js
class Foo {};
export default Foo;
foostorage.js
import Foo from "./foo.js";
class FooStorage {
constructor() {
this.storage = []; // Array of Foo objects
}
}
export default FooStorage;
foostorage.test.js
import Foo from "../src/foo.js";
import FooStorage from "../src/foostorage.js";
import { jest } from "#jest/globals";
jest.mock("../src/foo.js");
beforeEach(() => {
Foo.mockClear();
});
test("if the Foo constructor hasn`t been called", () => {
const storage = new FooStorage();
expect(Foo).not.toHaveBeenCalled();
});
output
if the Foo constructor hasn`t been called
TypeError: Foo.mockClear is not a function
7 |
8 | beforeEach(() => {
> 9 | Foo.mockClear();
| ^
10 | });
11 |
12 | test("if the Foo constructor hasn`t been called", () => {
at Object.<anonymous> (test/foostorage.test.js:9:6)
I have already tried to put jest.mock("../src/foo.js"); before import Foo from "../src/foo.js"; but the problem wasn't solved.
Edit:
I am using Jest v. 27.0.6 with jest-environment-node v. 27.0.6 and #types/jest v. 27.0.1.
I also use the nodejs arguments --experimental-modules and --experimental-vm-modules so that I can use ES6 imports. I don`t use Babel or anything else. Just plain JavaScript.
Automatic mocking is supposed to work like the documentation explains. If it doesn't and results in said error, this means that the module wasn't mocked, so it would fail with manual mock as well. This can happen because a mock wasn't performed correctly (different case or extension in module path), or jest.mock hoisting didn't work (most commonly because of misconfigured Babel transform).
The way this code differs from what's shown in the documentation is that jest global is commonly used, while here it is imported value, so it's not the same as using jest global. This is obvious and known reason for hoisting to work differently.
It can possibly be fixed by ordering dependencies in a way that doesn't interfere with expected execution order:
import { jest } from "#jest/globals";
jest.mock("../src/foo.js");
import Foo from "../src/foo.js";
...
The hoisting of jest.mock relies on undocumented, spec-incompliant hack that also depends on Jest version and a specific setup. It's not guaranteed to work because ES imports are expected to hoisted above other statements by specs, and there are no exclusions.
A dated but stable workaround is to switch to require that are regular JavaScript function calls and are evaluated as is:
let { jest } = require ("#jest/globals");
jest.mock("../src/foo.js");
let { default: Foo } = require("../src/foo.js");
...
And an efficient solution is to get rid of jest import and set up a generic Jest environment with respective globals, so jest.mock could be hoisted as expected regardless of Jest version and other circumstances.

Mocking a module imported in a vue component with Jest

I'm having some issues processing the documentation of Jest, because I expected this code to work:
import Vue from 'vue';
import Router from '#/router/index';
import OrdersService from '#/services/orders.service';
jest.mock('#/services/orders.service');
describe('OrdersItem.vue', () => {
beforeEach(() => {
// mockClear does not exist
OrdersService.mockClear();
});
it('should render expected list contents', () => {
// Orders.mock is undefined
OrdersService.getAll.mockResolvedValue([ ... ]);
// ...
However it does not. It fails as-if OrdersService was never mocked. I've also tried stuff like:
jest.mock('#/services/orders.service', () => jest.fn());
jest.mock('#/services/orders.service', () => { getAll: jest.fn() });
First one replaces the whole service with a mock function (I'd like to achieve that automatic mocking functionality mentioned in the docs, where all the methods from the original are auto-replaced with mock fn).
Second one fails same way as the .mock call with just the module path.
What am I doing wrong here and why?
orders.service skeleton:
import axios from 'axios';
import config from '../config/config.json';
import Order from '../models/order';
class OrdersService {
constructor(httpClient) {
this.httpClient = httpClient;
}
getAll() {
// ...
}
}
export default new OrdersService(axios);
It looks like there is an issue in with jest.mock (#4262) concerning moduleNameMapper for module resolvers, aliases, path, whatever you want to call using #/something.
// you cannot use a module resolver (i.e. '#')
jest.mock('#/services/orders.service');
// you must use the full path to the file for the import and mock
import OrdersService from '../../src/services/orders.service';
jest.mock('../../src/services/orders.service');
Stay tuned to updates on the issue, looks like the last update was on 9/28.
Secondly, provided you fix the issue above, you are exporting a class instance not the class itself, as is done in the Jest example. Therefore, you will not have access to the clearMock method on the OrdersService but rather you can call clearMock on each mocked method on the class instance.
// mockClear will be undefined
OrdersService.mockClear();
// mockClear is defined
OrdersService.getAll.mockClear();
If you want to export the instance as you are, you could just clear all mocks using jest.clearAllMocks in the beforeEach or loop through all methods and call mockClear on each. Otherwise exporting the class itself will give you access to OrdersService.mockClear which will ...
Clear all instances and calls to constructor and all methods (ref)
This seems to be useful in cases where the mocked class is being used/instantiated in another class that you are trying to test, as in the jest example.
All of this has been tested and confirmed using Jest v23.6 and vue-cli v3.0.4.
Since the OrdersService is an instance of the class, it will return an object and you would need to mock all the properties exposed by this object manually.
You could try with the following implementation to mock your function. Reference docs
OrdersService.getAll = jest.fn(()=>{
// mock implementation here;
});
Hope this helps :)
You could try calling jest.resetModules() in the beforeEach block, that might cause the mocked service to be used
Try to import everything with an alias and set the mock on the alias.
import * as OrdersModule from '#/services/orders.service';
OrdersModule.getAll = jest.fn()
I found it in the bible:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules

Jest mocking default exports - require vs import

I have seen questions referring to the mocking of default exports with jest around here, but I don't think this has already been asked:
When mocking the default export of a dependency of a module that is being tested, the tests suite fails to run if the module imports the dependency with the ES6 import statement, stating TypeError: (0 , _dependency.default) is not a function It succeeds, however, if the module uses a require().default call instead.
In my understanding, import module from location directly translates to const module = require(location).default, so I am very confused why this is happening. I'd rather keep my code style consistent and not use the require call in the original module.
Is there a way to do it?
Test file with mock:
import './modules.js';
import dependency from './dependency';
jest.mock('./dependency', () => {
return {
default: jest.fn()
};
});
// This is what I would eventually like to call
it('calls the mocked function', () => {
expect(dependency).toHaveBeenCalled();
});
Dependency.js
export default () => console.log('do something');
module.js (not working)
import dependency from './dependency.js';
dependency();
module.js (working)
const dependency = require('./dependency.js').default;
dependency();
You can use either es6 import or require js to import your js files in your jest tests.
When using es6 import you should know that jest is trying to resolve all the dependencies and also calls the constructor for the class that you are importing. During this step, you cannot mock it. The dependency has to be successfully resolved, and then you can proceed with mocks.
I should also add that as can be seen here jest by default hoists any jest.mocks to the top of the file so the order in which you place your imports does not really matter.
Your problem though is different. Your mock function assumes that you have included your js file using require js.
jest.mock('./dependecy', () => {
return {
default: jest.fn()
};
});
When you import a file using require js, this is the structure it has:
So assuming I have imported my class called "Test" using require js, and it has method called "doSomething" I could call it in my test by doing something like:
const test = require('../Test');
test.default.doSomething();
When importing it using es6 import, you should do it differently though. Using the same example:
import Test from '../Test';
Test.doSomething();
EDIT: If you want to use es6 import change your mock function to:
jest.mock('./dependecy', () => jest.fn());
the short answer for ES module if you want to use
import dependency from 'dependency'
jest.mock('dependency', () => ({
...jest.requireActual('dependency'),
__esModule: true,
default: jest.fn(),
}))
Have you tried something like this? I was dealing with the default export mocking for months until I found this.
jest.mock('./dependency', () => () => jest.fn());
The idea behind this line is that you are exporting a module that is a function. So you need to let Jest knows that it has to mock all your ./dependency file as a function, that returns a jest.fn()

Categories

Resources