Jest (testing) (mocking specifically)
Created: 2018-06-13 09:17:36 -0700 Modified: 2020-02-04 15:03:28 -0800
Mocking Promise implementations (reference)
Section titled Mocking Promise implementations (reference)There are also *Once variations to mock only a single time.
Mocking time (reference)
Section titled Mocking time (reference)(keyword: timers)
Jest has a function called useFakeTimers that can replace Sinon’s function with the same name.
Mocking files (reference)
Section titled Mocking files (reference)The reference covers how to do this. The gotchas I’ve run into are as follows:
- I tried mocking “pixi.js” by creating mocks/pixi.js, but apparently it needed to be called mocks/pixi.js.js
- I really struggled trying to get a non-js file to be mocked. In this case, I had “shader.frag” that I was importing statically from JavaScript, but I couldn’t do what Jest advises for mocking user modules (reference). As a workaround, I put this in jest.config.js:
module.exports = {
moduleNameMapper: {
‘shader.frag’: ‘<rootDir>/public/javascripts/mocks/shader.frag’,
},
};
Then, my mocks/shader.frag was just empty (except for a comment).
Update: this may look slightly better:
- Let’s say you have a module that you can’t import because it will immediately try calling into something that’s not allowed, e.g. pixi.js tries creating a white texture (which involves creating a canvas and getting its context), but that fails since there’s no DOM. I couldn’t write code like this in my mocks folder:
const mockedPixi = jest.genMockFromModule(‘pixi.js’);
module.exports = mockedPixi;
I think that when you run into an issue like this, you’ve got to manually mock any functions that you’re going to use (so in my case, I’d provide a module that has dummy functions for PIXI.Graphics and the likes). Alternatively, I could’ve installed canvas (which is what the error message suggests (Error: Not implemented: HTMLCanvasElement.prototype.getContext (without installing the canvas npm package))or used jest-canvas-mock.
- Mocked files are “immune” to “resetModules” (reference) since they’ll just be required again from the mocked file.
Mocking a module that’s used practically everywhere, e.g. a logger
Section titled Mocking a module that’s used practically everywhere, e.g. a loggerI ran into a scenario where I had a Lerna repository that had many modules, and the tests from all of them needed to mock the logger so that it wasn’t as noisy. I didn’t want to pollute each test with specific code for this, so here’s what I did instead:
Make the mocked module itself
Section titled Make the mocked module itselfIn my case, I had a “logger” module, so I added a source file that looks like this:
Make a jest.config.js to point to the mocked module (reference)
Section titled Make a jest.config.js to point to the mocked module (reference)Note: in my case: the “src” folder gets published to the “lib” folder for modules, so that’s why “lib” shows below:
That’s it! The mock is automatic thanks to moduleNameMapper, and if you extend a base config, you only really have to define this mapping in one spot.
Making a new mocked function (that’s not based on anything)
Section titled Making a new mocked function (that’s not based on anything)Just use “jest.fn(implementation)” (reference). Note that this is an alias for mockFn.mockImplementation.
HiDeoo: Note: you can also use jest.fn() with mockImplementationOnce, mockReturnValue[Once], etc. like you can do jest.fn().mockReturnValueOnce(3).mockReturnValueOnce(4).mockReturnValue(5), this would return 3 on first call, 4 on second and 5 after for all remaining calls.
Mocking dates
Section titled Mocking dateshttps://github.com/hustcc/jest-date-mock
Mocking exported functions
Section titled Mocking exported functionsIf you exported the functions via ES5 (“module.exports = {…}”)…
Section titled If you exported the functions via ES5 (“module.exports = {…}”)…It seems like this won’t work:
…it would still call into the real function, not the mocked function.
This gives you two options:
- Mock the whole file using jest.mock(‘@botland/aws’):
- Stop importing ”* as foo” and instead just import the object directly:
Just like with other mocks, you need to restore to the original function at the end, either via jest.restoreAllMocks(), jest.config.js → restoreMocks: true, or calling mockRestore on the individual mock.
If the function is defined in a separate file from the dev code using it…
Section titled If the function is defined in a separate file from the dev code using it…If you have something like this:
In your test code
If the function is defined in the same file as the dev code using it…
Section titled If the function is defined in the same file as the dev code using it…Suppose you have a file like this:
You want to test functionToTest while mocking functionToBeMocked. It is not possible to mock the functions from outside of this file in the way that you may want (look for “update 180204” on this page). You would either have to move the function to another file or do what’s shown at the link:
Then, just mock as you normally would
Mocking exported constants
Section titled Mocking exported constantsFor reference, here’s the scenario that I have:
I want to mock constantToBeMocked from my test code. Here’s a way to do that which doesn’t involve changing the code to be tested:
Notes:
- You cannot use any out-of-scope values whatsoever in this. For example, I have the number 4 hard-coded above; I can’t take the value from the line just before jest.mock since that’s out-of-scope.
- This means that modules like Lodash that you may want to use need to be required inside the mock function itself.
- This also means that any transpiled code that requires out-of-scope code needs to be changed. For example, if you’re not on a version of Node that allows the spread operator for objects, then it gets transpiled to use “_extend”. To fix that, you either have to work around the transpilation by using pure ES5 (e.g. Object.assign in this case) or upgrade Node to use the >ES5 code natively (and then get rid of any Babel plug-ins trying to convert it).
- If you want to use an out-of-scope value here, then you sort of need to reverse everything. For example, rather than say “const someVar = 5” outside of the scope and then mocking a constant to be “someVar”, you first need to set the mocked constant to 5, then require the mocked constant just like the dev code will and set someVar based on the mocked constant’s value. For example:
- Because of the hoisting mentioned above, you’ll need to call “jest.resetModules();” in every one of your tests. This could be done in the “beforeEach”. However, this will reset all local state of your modules. I ran into an issue originally where I imported my own custom logger, then called “logger.silenceAllLogs()”, but then “resetModules” would make them noisy again. To fix that, I needed to mock the “logger” module entirely.
- If you don’t do this, then it doesn’t matter whether you require or import at the top of the file; it’s going to mess up some of your tests.
Specifically for Bot Land constants, here’s how I mocked the constants since they’re just one potential export from @botland/shared:
I found this on StackOverflow, and I suppose that could have worked if I mocked the whole file, but I didn’t do that. Instead, I did the same thing that I’d do for mocked functions, which is to define a “lib” object:
Then, from the test code, I would do this:
I don’t really like this pattern because it results in every single constant that needs to be mocked being wrapped in a function for no reason.
Restoring a mock
Section titled Restoring a mockIf you mocked a function by using spyOn, then you can call “mockRestore”, otherwise you have to handle restoring by yourself if you just assigned a “jest.fn()“.
Ensuring middleware is called on a REST server
Section titled Ensuring middleware is called on a REST serverI had a situation like this:
In this situation, I just wanted to make sure that “getUserMiddleware” gets hit, not that it does what it’s supposed to. The reason for only checking that it gets hit is because I would have other tests ensure that the middleware works correctly, that way the unit test could focus on making sure getUser does the right thing.
Here are the steps I had to go through, and keep in mind that this is a combination of other notes in this OneNote:
- Make sure that getUserMiddleware resides in its own file as shown above. If it doesn’t, then relocate it to its own file.
- In the test code, add this:
By specifying “mockImplementation”, I make sure that the original function doesn’t get called. This is really helpful when middleware would have called into the database or done something that’s otherwise annoying to set up or takes a while to run.
Ensuring only a particular argument of spy/mock was specified
Section titled Ensuring only a particular argument of spy/mock was specifiedLet’s say you have a function like this:
function respondFailure(res) {
res.send(500, {error :‘who cares’});
}
If you mock this function and only want to verify that 500 was specified rather than the specific error message, then you cannot do this:
Instead, you must inspect the individual call: