Closed seibelj closed 8 years ago
Going back to the original request...
Couldn't you just wrap a proxy around an import *? eg
import * as test from './myfile.js';
const handler = {
/* Intercepts: getting properties /
get(target, propKey, receiver) {
console.log(GET ${propKey}
);
return 123;
},
/** Intercepts: checking whether properties exist */
has(target, propKey) {
console.log(`HAS ${propKey}`);
return true;
}};
const p = new Proxy(test);
On Tue, Jan 30, 2018 at 4:24 PM, Denis Loginov notifications@github.com wrote:
@WangHansen https://github.com/wanghansen could you add .mockImplementation() to your jest.spyOn()? As someone coming from Jasmine, I found this tip crucial to achieve the same functionality as Jasmine's spies. E.g.
const mockModuleFunction = jest .spyOn(module, 'function') .mockImplementation(() => 'hello');...expect(mockModuleFunction.mock).toBeCalled();
If you don't use mockImplementation(), then jest.spyOn() produces an object that is not a mock (afaiu) and it actually defers to the native implementation. If you do have to keep native implementation, maybe it's worth using
const moduleFunction = module.function;jest.spyOn(module, 'function').mockImplementation(moduleFunction);...
Not sure this is necessary, but fairly sure it should work.
β You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/facebook/jest/issues/936#issuecomment-361648414, or mute the thread https://github.com/notifications/unsubscribe-auth/AQRY9VXyHNYatwOOY6EV637WGQH9k5Plks5tP0I9gaJpZM4IPGAH .
--
Darren Cresswell Contract Developer | Develer Limited E-mail: darren@develer.co.uk Phone: Website: http://www.develer.co.uk
Please consider the environment before printing this email WARNING: Computer viruses can be transmitted via email. The recipient should check this email and any attachments for the presence of viruses. Develer Limited accepts no liability for any damage caused by any virus transmitted by this email. E-mail transmission cannot be guaranteed to be secure or error-free as information could be intercepted, corrupted, lost, destroyed, arrive late or incomplete, or contain viruses. The sender therefore does not accept liability for any errors or omissions in the contents of this message, which arise as a result of e-mail transmission.
WARNING: Although Develer Limited has taken reasonable precautions to ensure no viruses are present in this email, the company cannot accept responsibility for any loss or damage arising from the use of this email or attachments.
Develer Limited is a limited company registered in England and Wales. | Company Registration No. 09817616 | Registered Offices: SUITE 1 SECOND FLOOR EVERDENE HOUSE, DEANSLEIGH ROAD, BOURNEMOUTH, UNITED KINGDOM, BH7 7DU
@dinvlad @iampeterbanjo @SimenB Thanks again for all your help, but unfortunnately, none of the ways you have suggested worked. I wonder if it is because that the function is called in the form of next(err)
. The logic is, when a request failed, it will be passed into the errorHandler
by calling return next(err)
. For sure the function is being called because when I added console.log
, it is printing. But the tests suggest it is never called
@dinvlad I tried your method, it didn't work, but thanks for your help anyway. I was just wondering why do you need to call mockImplementation
, according to the official doc on jest.spyOn, you only call it when you want to override the original function.
@WangHansen yes you're right that it's only needed when one wants to override the original method. I was just throwing an idea for those situations.
One reason why it might have failed for you is asynchronicity. If your method is using callbacks and/or Promises (or async/await), then you need to make sure your expectations are actually executed before your test method terminates. There's a special method expect.assertions(N)
to assert that. Also make sure that your expectation is executed only after the code inside callbacks/promises has been called. I'm sure you've looked at that but just for the reference, https://facebook.github.io/jest/docs/en/asynchronous.html
It's unfortunate that mocking internally used fns as @seibelj describes is not possible without changing the module impl.
In my opinion, tests should not drive how business logic is implemented π (at least not to this degree)
Are there any plans to implement such behaviour in jest?
Hi, Kinda late to the whole discussion, but reading through the whole discussion - I still haven't managed to do that. @greypants 's promising solution didn't really work for me, as it still calls the original function. Have anything changed since the beginning of this discussion? Am I missing something?
I adapted @greypants's solution a little bit and it worked for me. Here's my code if it can help someone:
import Module from './module'
import { getJSON } from './helpers'
jest.mock('./helpers', () =>
Object.assign(require.requireActual('./helpers'), {
getJSON: jest.fn()
})
)
test('should use the mock', async () => {
getJSON.mockResolvedValue('foo')
await expect(module().someAsyncThingThatUsesGetJSON()).resolves.toEqual('foo')
})
test('should use the actual module', () => {
expect(module().someFunctionThatUsesAHelper()).toBe(true)
})
This still feels kinda hacky and the docs weren't that helpful. I'm grateful for Jest and the team behind it but it seems like a common use case that should at least have an "official" solution.
@sarahdayan, and whomever it may be of interest - I've ended up using babel-plugin-rewire. It took me a while to find that plugin, but 'twas a solution coherent enough to not feel hacky.
In most cases, we want to not mock one or more functions from a module. If you use global jest's mocking system you can achieve it using genMockFromModule
and requireActual
. Here is the example:
// __mocks__/myModule.js
const moduleMock = jest.genMockFromModule('./../myModule');
// in most cases we don't want to mock `someFunction`
moduleMock.someFunction = jest.fn(
(...args) => require.requireActual('./../myModule').someFunction(...args)
)
module.exports = moduleMock;
This solution allows using mocks for other functions from the module, using someFunction
original implementation when the whole module is mocked and also allows to mock the someFunction
function with mockImplementationOnce
or mockImplementation
API.
I have read all conversations above, but no one solution works for me. If you are still looking for solution for this test case, answer is babel-plugin-rewire, this plugin is to resolve that scenario case we discussed. Please take a look on that lib, you will thanks me back later.
So to summarize the whole thread above:
m
with functions f
, g
and h
where g
and h
call f
. We would like to mock f
so that g
and h
call the mock instead of the real f
. Unfortunately this is not possible to do directly unless f
is always called through exports
as cpojer described. This is impossible if your module uses ES6 import/export syntax in TypeScript (and I'm guessing the same is true in Babel).f
to another module m2
. Then m
will have a statement like import {f} from 'm2'
and when g
and h
call f
, they actually are calling m2.f
where m2 = require('./m2')
(that's how the Babel/TypeScript translation will look). This makes it possible to mock f
reliably as described by greypants. In other words, you can mock calls reliably only if they cross a module boundary. Note: greypants' solution now produces this error message: "The module factory of jest.mock()
is not allowed to reference any out-of-scope variables - Invalid variable access: __assign". I suspect this is a bug in Jest; as a workaround, use Object.assign
as shown below.Example of (2):
// module m.js
import {f} from './m2'
export function g() { return 'f returned ' + f(); };
// module m2.js
export function f() { return 'the real f'; }
// test.js
import * as m from './m'
jest.mock('./m2', () => Object.assign(
require.requireActual('./m2'), {
f: jest.fn().mockReturnValue('MOCK')
}));
test('mocked', () => {
expect(m.g()).toEqual('f returned MOCK');
});
While testing this I ran into #2649: calling jest.mock
inside a test has no effect, and if you call it at global scope then you cannot unmock
before other tests. Very annoying.
Thanks!! @sarahdayan Been looking for this for a while
If the docs are lacking, PRs are always welcome to clarify them π
Hi everyone!
I played a little bit around and had the following idea to solve that problem:
// m1.js
export const f = () => "original f"
// __mocks__/m1.js
const originalM1 = require.requireActual("../m1");
// set the original as a prototype
let mockModule: any = Object.create(originalM1);
const __setMock = (name, value) => {
mockModule[name] = value;
};
const __removeMock = (name) => {
Reflect.deleteProperty(mockModule, name);
};
// enhance the mocked module to allow overriding original exports
module.exports = Object.assign(mockModule, { __setMock, __removeMock });
// m1.test.js
import { f } from "./m1";
jest.mock("./m1");
test("mocking stuff", () => {
// here nothing is mocked - the original module is used
expect(f()).toBe("original f");
// override the export f of the module
require("./m1").__setMock("f", () => "mocked f");
expect(f()).toBe("mocked f");
// set it back to the original
require("./m1").__removeMock("f");
expect(f()).toBe("original f");
//override with another value
require("./m1").__setMock("f", () => "another mocked f");
expect(f()).toBe("another mocked f");
});
My 2 cents :
I tested a lot of solutions (if not all of them) and the only one that worked for me is this one (jest 23) :
// importedModule.js
export const funcB = () => {
return "not mocked";
};
export const funcA = () => {
return mockProxy.funcB(); // this works but it's soooo hacky
};
export const mockProxy = {
funcB
};
// moduleToTest.js
import { funcA } from "./moduleImported.js";
export default function() {
return funcA();
}
// test
let moduleImported = require("./moduleImported.js");
moduleImported.mockProxy.funcB = jest.fn(() => "mocked");
const funcToTest = require("./moduleToTest.js").default; // or import
it("should return mocked", function() {
expect(funcToTest()).toBe("mocked");
});
I came to the conclusion that it's not a good idea to try to do this because :
test.js
knows too much about implementation details of importedModule.js
mockProxy
by looking at importedModule.js
Has anyone found a solution for this that works?
I'm using:
"jest": "^21.2.1",
"jest-cli": "^21.2.1",
@jamesone have you read this https://github.com/facebook/jest/issues/936#issuecomment-410080252 ?
This worked for me, based on the answer by @thomaskempel:
In my case, I wanted to mock a dependency in node_modules, lets call it 'shared-components'. It exports a number of named components. I wanted to mock only a couple of these named exports, and leave the rest as the real thing.
So in __mocks__/shared-components.js
I have:
const original = require.requireActual('shared-components');
module.exports = {
...original,
moduleNameToOverride: jest.fn().mockImplementation(() => {
return 'whatever';
}),
}
In my case I was stubbing out the implementations. Hopefully this helps someone in the future.
I've faced the same issue recently, the conversation in this thread helped me to understand better and I summarized my findings here https://medium.com/@DavideRama/mock-spy-exported-functions-within-a-single-module-in-jest-cdf2b61af642
Inspired by @qwertie's solution
mockGet = jest.fn()
jest.mock('my-module', () => ({
...jest.requireActual('my-module'),
get: mockGet
}))
@MajorBreakfast that does work with React.lazy
?
const mockLazy = jest.fn();
jest.mock('React', () => ({
...jest.requireActual('React'),
lazy: mockLazy
}));
I still get ReferenceError: React is not defined
.
To mock an independent export function module: Export all of the individual functions part of the default export from the file.
Example: dataDao.js
function getData() function setData() function deleteData() export {getData, setData, deleteData}
Now you can import all of the functions from the file into your jest test by default naming;
dataDao.spec.js
import * as dataDao from '../dataDao'; // Spy on the modules referencing the assigned default name at the import jest.spyOn(dataDao, 'getData') jest.spyOn(dataDao, 'setData') jest.spyOn(dataDao, 'deleteData')
@vchinthakunta, that may work, but it looks like a violation of a major purpose of the export/import syntax: other modules will no longer be able to to import specific methods or data fields via
import { justThisThing } from 'someModule';
Am I missing something there?
@MajorBreakfast that does work with
React.lazy
?const mockLazy = jest.fn(); jest.mock('React', () => ({ ...jest.requireActual('React'), lazy: mockLazy }));
I still get
ReferenceError: React is not defined
.
I think 'React' should be lowercase here as it's referencing the import?
jest.mock('react'...)
I got my code working using the following which is simpler than other solutions i've seen here. It doesn't require you to use require
or set up default
exports.
helpers/navigation.js
export const initHeader = () => {
// initialise header
...
}
...
export const anotherHelperFunction = () => {
// do stuff
...
}
component that uses navigation.js
import { initHeader } from '../helpers/navigation';
jest.mock('../helpers/navigation');
...
describe('Test component', () => {
it('should reinitialise header', () => {
const mockHeaderInit = jest.fn();
initHeader.mockImplementation(mockHeaderInit);
const component = mountComponent(mockProps);
component.simulate('click');
expect(mockHeaderInit).toBeCalled();
}
}
mockGet = jest.fn() jest.mock('my-module', () => ({ ...jest.requireActual('my-module'), get: mockGet }))
ReferenceError: mockGet is not defined
4 | const mockGet = jest.fn();
5 | jest.mock('./browserStorage', () => ({
6 | ...jest.requireActual('./browserStorage'),
> 7 | get: mockGet,
| ^
8 | }));
jest.mock
is hoisted, use doMock
or
jest.mock('./browserStorage', () => ({
...jest.requireActual('./browserStorage'),
get: jest.fn(),
}));
const {get: mockGet} = require('./browserStorage');
This seems like a common enough problem. Is there something about this in the jest docs?
So, is there a solution for mocking a function within same module?
The following method worked for me, the trick is to reset the mocked function back at the end of the test. This example mocks verify function from jsonwebtoken module.
test('perform my test', async () => {
// save the real jwt.verify function
const verify = jwt.verify
// mock it.
jwt.verify = jest.fn().mockReturnValue({ sub: 0 })
// do the test
...
// set the real function back.
jwt.verify = verify
})
The following method worked for me, the trick is to reset the mocked function back at the end of the test. This example mocks verify function from jsonwebtoken module.
test('perform my test', async () => { // save the real jwt.verify function const verify = jwt.verify // mock it. jwt.verify = jest.fn().mockReturnValue({ sub: 0 }) // do the test ... // set the real function back. jwt.verify = verify })
where are you using the const verify
? anyways, this will only work if your mocked function isn't an exported const function
So, is there a solution for mocking a function within same module?
After a lot of searching, the solution for this issue is to store your exports in a single object that your function and mocks can reference to. The below articles all came to the same consensus.
https://github.com/facebook/jest/issues/936#issuecomment-438975674 https://medium.com/@qjli/how-to-mock-specific-module-function-in-jest-715e39a391f4 https://luetkemj.github.io/170421/mocking-modules-in-jest
It's surprising to me that no one has mentioned a different solution that, if it is workable for your code, completely eliminates all these problems and makes testing the desired code very, very simple:
Move the single function you want to mock into its own module.
Seriously. If your module is written such that you need to test inner parts of it separately from other inner parts, then almost certainly your class is violating the Single Responsibility Principle (yes, it's not a real class, but the module is functioning like a class, modules being a unit container of code). Split that sucker, and boom, you can mock the requires.
If the mocked function relies on a bunch of private state, this still isn't a good reason not to split your module somehow. The very fact that it relies on a bunch of internal state implies, to me, that the concerns of the module are not clearly thought out. Perhaps there is even a third module to split out, that represents some kind of data class or DTO, which can be passed in as an argument.
Also, are you exporting functions just for testing that would otherwise be private? Why is it that external code would call the mocked function directly, but also call the other functions that themselves need to call it? I bet there is some kind of disconnect going on here. Maybe the mocked function needs to stay but all the functions need to be split in half with the half removed going into another module. You get the idea.
When testing gets very hard, it's almost always a sign of refactoring being needed...
No testing tomfoolery required:
const tomfoolery = require('tomfoolery'); // no longer required
After reading this thread and testing the proposed solutions, I still can't get this to work. From what I've read some people made this work but I can't figure out how.
Could someone tell me the code I need to add to the following example to make the tests pass?
// a.js
export const foo = () => 'foo-' + bar()
export const bar = () => 'bar'
// a.test.js
import {
foo,
bar
} from './a'
describe('foo', () => {
it('should return foo-MOCKED_BAR', () => {
expect(foo()).toBe('foo-MOCKED_BAR')
})
it('should have the mocked implementation of bar', () => {
expect(bar()).toBe('MOCKED_BAR')
})
})
describe('bar', () => {
it('should have the original implementation of bar', () => {
expect(bar()).toBe('bar')
})
})
describe('foo and bar together', () => {
it('should have the original implementation of both', () => {
expect(foo()).toBe('foo-bar')
})
})
Thanks!
I wanted to only mock a single lodash method like lodash.random and was able to do it easily with:
module.js
const lodash = require('lodash');
module.exports = function() {
return lodash.random();
}
test.js
const lodash = require('lodash');
const module = require('./module.js);
it('mocks lodash', () => {
jest.spyOn(lodash, 'random').mockImplementationOnce(() => {
return 2;
});
expect(module()).toEqual(2)
});
Hope that helps :)
Something that worked for our team working with typescript was to create a const
we export instead of exporting the function directly.
Not working:
export function doSomething(a, b) {}
Working:
export const doSomething = function (a, b) {}
I changed export like what @arbielsk do, it works! But I dont know what's the difference between two kinds of export...
@dgrcode Did you ever find a solution to your example? As far as I can tell, what you're trying to do is not supported by mocking via Jest. Specifically, I think that mocking in just is basically rewiring imports so that external views of the module see the mocked methods. However, in your example, foo
and bar
are in the same module and so foo
's view of bar
can't be mocked out.
I believe that your options are either:
1) Re-organize your code so that foo
imports a module that includes bar
2) Use babel-plugin-rewire
Please correct me if I'm misunderstanding!
I had a slightly different requirement: I wanted to mock an entire module except for one function. Using @MajorBreakfast's solution as a starting point, I came up with the following:
jest.mock('my-module', () => ({
...jest.genMockFromModule('my-module'),
myFunction: jest.requireActual('my-module').myFunction
}))
@dgrcode Did you ever find a solution to your example? As far as I can tell, what you're trying to do is not supported by mocking via Jest. Specifically, I think that mocking in just is basically rewiring imports so that external views of the module see the mocked methods. However, in your example,
foo
andbar
are in the same module and sofoo
's view ofbar
can't be mocked out.I believe that your options are either:
- Re-organize your code so that
foo
imports a module that includesbar
- Use babel-plugin-rewire
Please correct me if I'm misunderstanding!
That was actually my case My understanding of how modules could be mocked was all messed up π€¦ββ
Basically If 2 function are in the same module, and calling one another
If foo
is calling bar
function bar() {
return 'some-result'
}
function foo(){
return bar() // <-- the function I want to mock
}
Put bar
in a new file (to be mocked)
I moved the bar
method in a new file, and now I could use many of the examples above
Big thanks to @yoni-abtech in making me understand that π€£
The way I see it after reading this whole thread and testing over and over, there are 3 options....
const
This requires you to mandate the use of function expressions over declarations. Fortunately, the func-style
eslint rule has got your back.
Using export const
allows you to spyOn
functions that are used by other functions from within the same module.
// hello.js
export const message = () => {
return 'Hello world';
}
export const foo = () => {
return message();
}
// hello.test.js
import * as testModule from './hello.js';
describe('test spyon with function expressions', function () {
afterAll(() => {
jest.restoreAllMocks();
});
it('should NOT mock message in foo', function () {
const actual = testModule.foo();
expect(actual).toBe('Hello world');
});
it('should mock message in foo', function () {
jest.spyOn(testModule, 'message').mockReturnValue('my message');
const actual = testModule.foo();
expect(actual).toBe('my message');
expect(testModule.message).toHaveBeenCalledTimes(1);
});
});
rewire
babel pluginIf you don't want to mandate the use of function expressions (i.e. using const
) this could be a good approach.
This allows you to rewire (aka mock) functions from the same module. I could imagine the code would look something like that below but haven't tested it. Also from their docs it looks like you can rewire functions in the same module that are not even exported from the module π, imagine the example below without exporting the message
function.
Example:
// hello.js
export function message() {
return 'Hello world';
}
export function foo() {
return message();
}
// hello.test.js
import * as testModule from './hello.js';
describe('test rewire api', function() {
it('should NOT mock message in foo', function () {
const actual = testModule.foo();
expect(actual).toBe('Hello world');
});
it('should mock message in foo', function () {
testModule.__RewireAPI__.__Rewire__('message', jest.fn().mockReturnValue('my message'));
const actual = testModule.foo();
expect(actual).toBe('my message');
expect(testModule.message).toHaveBeenCalledTimes(1);
testModule.__RewireAPI__.__ResetDependency__('message');
});
});
See the docs for examples.
Note: Requires babel transpilation
This option is the least favorable but would clearly work just fine with the typical mock
functionality.
PS: Although this tread was very enlightening and often entertaining, I hope this synopsis mitigates the need for others to read the whole thread. βοΈ
Thanks @nickofthyme, you just ended a couple days of banging my head against this.
@nickofthyme your option 1
fails in both my app, and create react app
:
FAIL src/hello.test.js
test spyon with function expressions
β should NOT mock message in foo (3ms)
β should mock message in foo (6ms)
β test spyon with function expressions βΊ should mock message in foo
expect(received).toBe(expected) // Object.is equality
Expected: "my message"
Received: "Hello world"
17 | const actual = testModule.foo();
18 |
> 19 | expect(actual).toBe("my message");
| ^
20 | expect(testModule.message).toHaveBeenCalledTimes(1);
21 | });
22 | });
at Object.toBe (src/hello.test.js:19:20)
Test Suites: 1 failed, 1 total
Tests: 1 failed, 1 passed, 2 total
Snapshots: 0 total
Time: 1.848s
Ran all test suites related to changed files.
@danielhusar You appear to be correct. Sorry, I should have tested this with CRA.
I got the mocking described in option 1 to work here. Test it using the yarn test:hello
script.
> yarn test:hello
yarn run v1.16.0
$ jest --config=jest.config.js -t=hello --verbose
PASS src/hello/hello.test.ts
test hello
β should NOT mock message in foo (3ms)
β should mock message in foo (1ms)
Test Suites: 1 skipped, 1 passed, 1 of 2 total
Tests: 1 skipped, 2 passed, 3 total
Snapshots: 0 total
Time: 1.392s
Ran all test suites with tests matching "hello".
β¨ Done in 2.36s.
It requires using a custom jest.config.js
file using ts-jest
and calling jest --config=./jest.config.js
directly, not through react-scripts
. I am not sure how jest is configured in the react-scripts
but I think there may be a way to update the config somehow.
This fix removes the transforms for *.css
and *.svg
files, so ignore the App.tsx
errors.
Is there anything particular that needs to be done in order to make it work? I would say I have pretty standard setup (without ts) and it doesn't work out of the box.
Iβll look at it a little more tonight and see if thatβs possible.
@danielhusar I looked into last night and couldn't get an out of the box solution. The key is the jest config transformer
which CRA allows you to override in package.json#jest
. The js and ts files are transpiled using babel-jest
but react-scripts
blocks you from using a .babelrc
config file and setting the test
env which they set in react-scripts test
here.
I wish I could dig deeper but don't have the time right now.
Hmm Im still bit struggling to make it work (on my custom setup, not the cra). (latest version of both jest and babel-jest)
This is my jest config:
module.exports = {
name: 'my-app',
testURL: 'http://localhost/',
setupFiles: ['<rootDir>/setup-jest.js'],
setupFilesAfterEnv: ['<rootDir>/setup-test.js'],
testMatch: ['**/__tests__/**/*.test.js?(x)', '**/?(*.)+(spec|test).js?(x)'],
testEnvironment: 'jest-environment-jsdom-fifteen',
snapshotSerializers: ['enzyme-to-json/serializer'],
globals: {
ENV: 'test',
},
transform: {
'^.+\\.[t|j]sx?$': 'babel-jest',
},
};
And my babelrc:
{
"presets": [
"@babel/react",
["@babel/env", {
"corejs": "3",
"useBuiltIns": "entry",
"loose": true,
"exclude": [
"es.string.split"
]
}],
"@babel/flow"
],
"plugins": [
"array-includes",
"lodash",
"@babel/plugin-syntax-dynamic-import",
"@babel/plugin-syntax-class-properties",
"@babel/plugin-proposal-class-properties",
"@babel/plugin-proposal-object-rest-spread",
"@babel/plugin-proposal-optional-chaining"
],
"env": {
"test": {
"plugins": ["dynamic-import-node"]
}
}
}
For anyone struggling with option 1, it's important to use () => { return expression }
instead of () => (expression)
function.
I got option 1 to work by modifying it to:
import * as test from './test';
export const message = () => {
return 'Hello world';
}
export const foo = () => {
return test.message();
}
Not pretty but it should work.
I'm struggling with something that I think should be both easy and obvious, but for whatever reason I can't figure it out.
I have a module. It exports multiple functions. Here is
myModule.js
:I unmock the module for testing.
jest.unmock('./myModule.js');
However, I need to mock
foo
, because it makes ajax calls to my backend. I want every function in this file to remain unmocked, expect forfoo
, which I want to be mocked. And the functionsbar
andbaz
make calls internally tofoo
, so when my test callsbar()
, the unmockedbar
will call the mockedfoo
.It appears in the jest documentation that calls to
unmock
andmock
operate on the entire module. How can I mock a specific function? Arbitrarily breaking up my code into separate modules so they can be tested properly is ridiculous.