New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
jest.mock factory doesn't work inside a test #2582
Comments
|
Same thing with In the docs, I can read
But... A test is a code block, right? So in my case, I don't expect to see any differences. Here's a full test
|
Could you provide a repro of this in a GH repository? |
Sure. Here it is: https://github.com/tleunen/jest-issue-2582 Both tests render "Disabled" even though one of then has a mock to renders "Enabled" instead. Thanks. |
Glad I found this issue, I was breaking my head why For me it also applies to --edit It's obviously necessary to hoist the In my mind it would make it more explicit if |
So Generally we advise to setup different mocks in @cpojer could elaborate on this in detail, and if we want to hoist the calls to the upper scopes. |
This is because you are requiring your modules when the module initializes (using import). beforeEach(() => { // or the specific test
jest.mock('MyModule', () => …);
const MyModule = require('MyModule');
…
}); etc. |
If you were to do this in beforeEach, I'm unclear how you'd differentiate tests (so how would you be giving a different mock for each test?) Putting it inside the tests themselves works of course. |
Can you then pass mocked module inside the tested component? |
What if I am not importing/requiring the file I want to mock (e.g. a dependency of other file I am importing) but I want it scoped to a describe/it block? Or even if I want to mock differently for beforeEach/beforeAll test? Are those cases possible?
|
In that case, you need to require A after mocking B. (Not using |
didn't work for me, was still seeing the original mock of B pass through to the result |
@GoldAnna have you found a workaround for this issue? |
@alayor I have not |
As many times as I've run into this problem, I'm now convinced I'm either not testing properly, not using That said, the following works for me, and all of the following tests pass. Note that to get back to the original version of // Module A
const ModuleB = require('./moduleB');
const ModuleA = function() {
this.title = new ModuleB().title;
return this;
};
module.exports = ModuleA;
// Module B
const ModuleB = function() {
this.title = 'Module B - Original'
return this;
};
module.exports = ModuleB;
// Tests
describe('Jest a few tests', () => {
it('should do something', () => {
jest.resetModules();
jest.mock('./moduleB', () => function() {
this.title = 'Module B - Mock 1'
return this;
});
const ModuleA = require('./moduleA');
const moduleA = new ModuleA();
expect(moduleA.title).toEqual('Module B - Mock 1');
});
it('should do something else', () => {
jest.resetModules();
jest.mock('./moduleB', () => function() {
this.title = 'Module B - Mock 2'
return this;
});
const ModuleA = require('./moduleA');
const moduleA = new ModuleA();
expect(moduleA.title).toEqual('Module B - Mock 2');
});
it('should do something original', () => {
jest.resetModules();
jest.unmock('./moduleB');
const ModuleA = require('./moduleA');
const moduleA = new ModuleA();
expect(moduleA.title).toEqual('Module B - Original');
});
}); |
Hey, Nothing here works for me. Thank you |
I think this is still an issue that should maybe be reopened as a feature request. I want to write tests in isolation. Dumping things in a mocks folder or rewiring with a beforeEach usually ends up with a junk drawer of mocks/weird data instantiation that gets lugged around into every test. I want to write a small mock and make a small test pass. |
We have no real way of isolating individual tests (considering |
Had a struggle with multiple methods here to refactor a test previously written in mocha and proxyquire, end up with separating the test into different files for different mock. |
@gcox Have you tried mocking (and requiring moduleA after) at the top-level of the module and then changing the mock's implementation for each test that needs different behavior? You can use mockImplementation to do so. This is how we've solved this in our test suites. @antgonzales I think that's difficult to do if module imports are hoisted to the top of a module. Modifying a previously-imported module's references seems non-trivial to impossible in node. |
@jkomusin Definitely. That's just not what the OP was asking for, IMO. I believe they were asking, specifically, "How do I force |
To change return value of a mock between tests, you can do something like this: jest.mock('whatever');
// Get the mock function
const whatever = require('whatever');
test('test 1', () => {
whatever.mockImplementation(() => 'hello');
});
test('test 2', () => {
whatever.mockImplementation(() => 'world');
}); |
@rafaeleyng but @SimenB doesn't work if what you export from the module is not a function... |
Jasmine Jasmine Example:
|
What I don't quite understand when it comes to mocking modules is that it is always described as if you want to use the module primarily in your test-functions. E.g. if you look on the example code for the
In this example, you have access to the newly mocked version of Seems to be related: #3236 |
Tip - if you have a module that exports a primitive value, e.g.: export const isUserAdmin = getSetting('admin'); And you want to use a mock value instead, in the test, then a simple require and assignment seems to do the trick: const auth = require('../auth');
describe('deleteUser', () => {
it('can delete if admin', () => {
auth.isUserAdmin = true;
// call method that depends on isUserAdmin value
// and assert on the result
});
}); |
If you want to mock an object that is used indirectly by the code tested the import myModuleToTest from './myModuleTotest'
describe('Given my module', () => {
it('property1 will work as expect', () => {
// Testing parts of the module that don't need to be mocked
})
it('property2 will work as expected', () => {
jest.doMock('./myOtherModule', () => {
return {
__esModule: true,
default: 'default2',
foo: 'foo2',
};
});
import('./myOtherModule').then(myOtherModule => {
// I'm not interested on the mocked module myOtherModule but on the module that makes use of it
myModuleToTest.doSomethingToSomeProperty(); // At this point myOtherModule's original module and not its mocked version will be used by myModuleToTest
expect(myModuleToTest.someProperty).toBe('thisWillFail'); // The test won't pass because the mocked version wasn't used
});
});
}); As of Jest 26 there is no way to mock more than once a module exporting an The only solution then is having more than one test file to test the same module. |
You have to import myModuleToTest after your mock because if it imports
before the mock does not make sense. So, don't use import .... on top or
inside the callback because it's hoisted anyway.
…On Tue, Jul 7, 2020, 10:24 PM Antonio Redondo ***@***.***> wrote:
So basically, if you want to mock an object that is used indirectly by the
code you test the jest.doMock() function won't work:
import myModuleToTest from './myModuleTotest'
it('will work', () => {
jest.doMock('./myOtherModule', () => {
return {
__esModule: true,
default: 'default2',
foo: 'foo2',
};
});
return import('../myOtherModule').then(myOtherModule => {
// I'm not interested on the mocked module myOtherModule but on the module that makes use of it
myModuleToTest.doSomethingToSomeProperty(); // At this point myOtherModule's original module and not its mocked version will be used by myModuleToTest
expect(myModuleToTest.someProperty).toBe('thisWillFail'); // The test won't pass because the mocked version wasn't used
});});
As of Jest 26 there is no way to mock a module exporting an Object (I
mean something else than a Function) that is used indirectly. Is this
correct or I am missing something? The only solution then is having more
than one test file to test the same module.
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#2582 (comment)>, or
unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAJR3UW6HARW44ZLKAUB7PLR2N77NANCNFSM4C4I7QSQ>
.
|
None of the solutions mentioned seemed to work for me, so I added this above my
Then I updated the mocked data that was being passed into the |
Just encountered this really tedious bit of error (react), but I think I figured it out. The solution was staring at me all this time. this bit of info in the documentation is the key:
This means that if you happen to have imported a module that also uses/imports that module (that you are about to mock) in the setup file, you will not be able to mock that module! In my case, I was refactoring my code so that everything that relates to testing is in one export (index) file (including the methods I was about to mock! It's a Graphql + MSW setup). |
After going through a number of solutions online, a lot of documentation , and none of them working out, I found something that works for me. Please feel free to update / improve on it So instead of mocking at the top, I started creating a mockRender variable and run it beforeEach. Code example:
This is a sample of what I have implemented. Hope it helps out any one stuck with this ✌ |
This is what worked for me to mock a custom hook being used inside code for specific test suite. import * as useCustomHook from './useCustom'
describe('My test suite', () => {
const mockSaveMethod = jest.fn();
const mockDeleteMethod = jest.fn();
const clickOnElementByTestId = (testId: string) => {
fireEvent.click(screen.getByTestId(testId));
};
beforeEach(() => {
jest.spyOn(useCustomHook, 'useCustom').mockImplementation(() => {
return {
saveMethod: mockSaveMethod,
deleteMethod: mockDeleteMethod
};
});
});
test('should call save on clicking save button', async () => {
render('<MyComponent />');
clickOnElementByTestId('testIds.saveButton');
await waitFor(() => {
expect(mockSaveMethod).toHaveBeenCalledTimes(1);
});
});
test('should call publish on clicking publish button', async () => {
render('<MyComponent />');
clickOnElementByTestId('testIds.publishButton');
await waitFor(() => {
expect(mockDeleteMethod).toHaveBeenCalledTimes(1);
});
});
}); and export function useCustom(){
const saveMethod=()=>{};
const deleteMethod=()=>{};
return {
saveMethod,
deleteMethod
}
} and the component under testing export function MyComponent(){
const { saveMethod, deleteMethod } = useCustom();
...
...
} |
@janl 's answer brought me toward the right direction. The solution which works for me is to call test-init.js jest.mock("my-module-name")
//... other stuff my-component.test.js // here you will get the mocked version
const myLibOrModule = require('my-lib-or-module');
describe("Me tests", () => {
test('test 1', async () => {
// depending on what your lib or module exports (it can export a function or an object)
// you can mock anything it exports
// if the module exports a function
myLibOrModule.mockImplementation((...args) => {
// you custom return value
})
// OR
myLibOrModule.mockReturnValue({
// your obj
})
//if your module exports an object with function fields
myLibOrModule.myFunction.mockImplementation((...args) => {
// you custom return value
})
//...your tests
});
test('test 2', async () => {
// in the same way as in test 1, you can write mocks for this specific test
// if the module exports a function
myLibOrModule.mockImplementation((...args) => {
// you custom return value
})
// OR
myLibOrModule.mockReturnValue({
// your obj
})
//if your module exports an object with function fields
myLibOrModule.myFunction.mockImplementation((...args) => {
// you custom return value
})
//...your tests
});
}) |
another solution to this issue is creating
require('A')
jest.mock('module_to_mock')
// .....
// tests here |
This worked for me. I had stopped importing the module (that I'm testing) at the top level, and then just do an import in the test level instead. import { testUserInit } from "../test-data/test-user-init";
describe("create", () => {
beforeEach(() => {
jest.resetModules();
});
describe("happy path", () => {
let result: string;
beforeAll(async () => {
jest.doMock("/opt/nodejs/dynamo.config", () => ({
dynamoClient: {
put: jest.fn().mockReturnValueOnce({
promise: jest.fn().mockResolvedValueOnce(null),
}),
},
TABLE_NAME: "test-table-name",
}));
const userDao = await import("../user.dao");
result = await userDao.create(testUserInit);
});
it("should return the user ID", () => {
expect(result).toBe(testUserInit.id);
});
});
// ...
});
|
@apperside solution is the one that finally worked me me. Set up "jest": {
"preset": "jest-expo",
"transformIgnorePatterns": [
"node_modules/(?!((jest-)?react-native|@react-native(-community)?)|expo(nent)?|@expo(nent)?/.*|@expo-google-fonts/.*|react-navigation|@react-navigation/.*|@unimodules/.*|unimodules|sentry-expo|native-base|react-native-svg)"
],
"setupFiles": [
"./jestSetup.ts"
]
}, Then in jest.mock('moduleToBeMocked'); Then in test file const mockedModule = require('moduleToBeMocked');
test('Some test', async () => {
mockedModule.mockedModuleFunction.mockImplementation(
() => "hello world",
);
const result = mockedModuleFunction();
expect(result).toBe("hello world");
}); Should note that this also works if function otherFunction() {
return mockedModuleFunction()
} Then this will also work. const mockedModule = require('moduleToBeMocked');
test('Some test', async () => {
mockedModule.mockedModuleFunction.mockImplementation(
() => "hello world",
);
const result = otherFunction();
expect(result).toBe("hello world");
}); |
By using jest.mock and jest.spyOn, we can mock inside every test with different implementation
|
The only way it looks like working when it needs to mock a module used as a transitive dependency, is to mock the transitive module as usual. Then to override its possible results, just create a spy on the mocked module. |
I will share my example for those in the same situation. I tried with the following codes: // utils.ts
import axios from "axios";
export async function get(apiUrl: string): Promise<any> {
try {
const response = await axios.get(apiUrl);
return response.data;
} catch (error) {
return null;
}
} // utils.test.ts
import { get } from "./utils";
describe("utils tests", () => {
describe("get() tests", () => {
test("should return product when request is success", async () => {
jest.mock("axios");
const axios = require("axios");
const mockedAxios = jest.mocked(axios);
const apiUrl = "https://dummyjson.com/product/1";
const mockProduct = {
id: 1,
title: "iPhone 9",
description: "An apple mobile which is nothing like apple",
price: 549,
discountPercentage: 12.96,
rating: 4.69,
stock: 94,
brand: "Apple",
category: "smartphones",
};
mockedAxios.get.mockResolvedValue({
data: mockProduct,
});
const result = await get(apiUrl);
expect(mockedAxios.get).toHaveBeenCalledWith(apiUrl);
expect(result).toStrictEqual(mockProduct);
});
});
}); And I couldn't mock it. I realized later. I needed to mock axios before importing the file I'm using it. So: // utils.test.ts
describe("utils tests", () => {
describe("get() tests", () => {
test("should return product when request is success", async () => {
jest.mock("axios");
const axios = require("axios");
const mockedAxios = jest.mocked(axios);
const { get } = require("./utils");
const apiUrl = "https://dummyjson.com/product/1";
const mockProduct = {
id: 1,
title: "iPhone 9",
description: "An apple mobile which is nothing like apple",
price: 549,
discountPercentage: 12.96,
rating: 4.69,
stock: 94,
brand: "Apple",
category: "smartphones",
};
mockedAxios.get.mockResolvedValue({
data: mockProduct,
});
const result = await get(apiUrl);
expect(mockedAxios.get).toHaveBeenCalledWith(apiUrl);
expect(result).toStrictEqual(mockProduct);
});
});
}); I can move it to // utils.test.ts
describe("utils tests", () => {
describe("get() tests", () => {
let mockedAxios: jest.Mocked<any>;
beforeEach(() => {
jest.mock("axios");
const axios = require("axios");
mockedAxios = jest.mocked(axios);
});
test("should return product when request is success", async () => {
const { get } = require("./utils");
const apiUrl = "https://dummyjson.com/product/1";
const mockProduct = {
id: 1,
title: "iPhone 9",
description: "An apple mobile which is nothing like apple",
price: 549,
discountPercentage: 12.96,
rating: 4.69,
stock: 94,
brand: "Apple",
category: "smartphones",
};
mockedAxios.get.mockResolvedValue({
data: mockProduct,
});
const result = await get(apiUrl);
expect(mockedAxios.get).toHaveBeenCalledWith(apiUrl);
expect(result).toStrictEqual(mockProduct);
});
});
}); And I can override the global mock (simplified): import { get } from "./utils";
jest.mock("axios", () => {
return {
get: jest
.fn()
.mockRejectedValueOnce(new Error("Error occured when fetching data!")),
};
});
describe("utils tests", () => {
beforeEach(() => {
jest.resetModules();
});
describe("get() tests", () => {
test("should return product when request is success", async () => {
const apiUrl = "https://dummyjson.com/product/1";
const mockProduct = {
id: 1,
title: "iPhone 9",
description: "An apple mobile which is nothing like apple",
price: 549,
discountPercentage: 12.96,
rating: 4.69,
stock: 94,
brand: "Apple",
category: "smartphones",
};
jest.doMock("axios", () => ({
get: jest.fn().mockResolvedValueOnce({
data: mockProduct,
}),
}));
const { get } = require("./utils");
const result = await get(apiUrl);
expect(result).toStrictEqual(mockProduct);
});
test("should return null when request is failed", async () => {
const apiUrl = "https://dummyjson.com/product/1000";
const result = await get(apiUrl);
expect(result).toBeNull();
});
});
});
|
worked like magic |
Another solution could be using an external variable that is changed inside each test. let locale: string
jest.mock('@/hooks/useTranslation', () => ({
useTranslation: () => {
return {
locale,
}
},
}))
describe('Test suite', () => {
it('test english language', () => {
locale = 'en'
const component = render(<Component />)
...
})
it('test italian language', () => {
locale = 'it'
const component = render(<Component />)
...
})
}) |
@baspinarenes 's solution worked for me! Thanks! |
it works for me
|
Worked to me as well, great! |
Do you want to request a feature or report a bug?
Bug
What is the current behavior?
It seems the way to create a mock with a factory doesn't work inside
test
orit
. It works only when the mock is defined at the root level of the file.Here's my example of a mock:
What is the expected behavior?
Mocking a file inside a test should work.
Please provide your exact Jest configuration and mention your Jest, node, yarn/npm version and operating system.
Jest 18.0.0, Node 7.4, macOS
The text was updated successfully, but these errors were encountered: