Why is Jest still requiring a mocked module? - javascript

I am mocking a module using Jest because it contains code that shouldn't run in the test. However I can see from the output that code in the module is being run.
// foo.js
console.log('Hello')
// test.js
jest.mock('./foo')
const foo = require('./foo')
test.todo('write some tests')
Console output
PASS test.js
✎ todo 1 test
console.log foo.js:1
Hello
What's up with that?

This has tripped me up a couple of times.
If you don't provide a mock implementation to jest.mock it will return an object which mirrors the exports of the mocked module but with every function replaced with a mock jest.fn(). This is pretty neat as it is often what you want. But in order to determine the exports of the module, it must first require it. This is what is causing the console.log to be run.
Two possible solutions:
Don't run code in the top level of the module: instead export a function which runs the code.
Provide your own mock implementation so it doesn't need to introspect the module jest.mock('./foo', () => {})

Related

Jest mocking module implementation with .mockImplemetation

Can anyone help here. I am really frustrated with how the mockImplementation works.
So, first of all I am using jest for node testing. I am using the commonjs modules. what I wanna do is that I am trying to mock a module using mockImplementation() and change its implementation between different tests according to this documentation here: https://jestjs.io/docs/en/es6-class-mocks#replacing-the-mock-using-mockimplementation-docs-en-mock-function-api-mockfnmockimplementationfn-or-mockimplementationonce-docs-en-mock-function-api-mockfnmockimplementationoncefn.
My code look like this:
const exportBigQueryTableModule =require('../repository/exportBigQueryTable')
jest.mock('../repository/exportBigQueryTable')
describe('deleting files on table export fail', () => {
mockExportBigQueryTable = jest
.fn(() => Promise.resolve())
.mockResolvedValueOnce()
.mockRejectedValueOnce(new Error('Could not export table'))
exportBigQueryTableModule.mockImplementation(mockExportBigQueryTable)
it(' should do some test', () => {})
})
The problem here is that looks like that this line jest.mock('../repository/exportBigQueryTable') create for me a default mock kind of jest.fn() and the module is always loaded with that default function. So the mock function that I did provide on the test using the mockImplementation never overrides the previous one, I do not get what is the problem here. Why the same exmaple on the official documentation works the only difference is that it uses es6 modules on the doc sample.
I am not sure if I am missing something here.

Jest mock module for single import

When using jest with ES6 modules and babel-jest, all the jest.mock calls are hoisted.
Let's say I'd like to mock fs module for the tested class, but preserve the original implementation for the rest of the modules (for example some utils that I use during the test).
Consider the following example:
class UnderTest {
someFunction(){
fs.existsSync('blah');
}
}
class TestUtility {
someOtherFunction(){
fs.existsSync('blahblah');
}
}
the test:
it('Should test someFunction with mocked fs while using TestUtility'', () => {
testUtility.someOtherFunction(); // Should work as expected
underTest.someFunction(); // Should work with mock implementation of 'fs'
})
Now, one would expect that with the following approach the fs module will be mocked for UnderTest but not for TestUtility.
import {TestUtility} from './test-utility';
jest.mock('fs');
import {UnderTest } from './under-test';
However, due to the hoisting, fs module will be mocked for all the modules (which is undesirable).
Is there any way to achieve the described behavior?
To opt out from mocking a module in a test jest.doMock(moduleName, factory, options) and jest.dontMock(moduleName) should be used.
jest.doMock(moduleName, factory, options)
When using babel-jest, calls to mock will automatically be hoisted to the top of the code block. Use this method if you want to explicitly avoid this behavior.
jest.dontMock(moduleName)
When using babel-jest, calls to unmock will automatically be hoisted to the top of the code block. Use this method if you want to explicitly avoid this behavior.
So in your case I would try something like
beforeEach(() => {
jest.resetModules();
});
it('Should test someFunction with mocked fs while using TestUtility'', () => {
jest.dontMock('fs');
testUtility.someOtherFunction(); // Should work as expected
jest.doMock('fs', () => {
return ... // return your fs mock implementation;
});
underTest.someFunction(); // Should work with mock implementation of 'fs'
})
You should probably use jest's requireActual:
const fs = jest.requireActual('fs'); // Unmockable version of jest
class TestUtility {
someOtherFunction(){
fs.existsSync('blahblah');
}
}
From the documentation:
Jest allows you to mock out whole modules in your tests, which can be useful for testing your code is calling functions from that module correctly. However, sometimes you may want to use parts of a mocked module in your test file, in which case you want to access the original implementation, rather than a mocked version.

Mock doesn't work, while reassignment does

I have the following Jest/Enzyme test:
const mockCheckMyFunctionality = jest.fn();
jest.mock('../modules/MyFunctionality', () => ({
checkMyFunctionality: mockCheckMyFunctionality
}));
const wrapper = shallow(
<App initialProps={mockInitialProps} />
);
expect(mockCheckMyFunctionality).toHaveBeenCalledTimes(1);
This will fail with TypeError: Cannot read property 'onNextTick' of undefined. The error message itself is not relevant, but it just shows that the real MyFunctionality.checkMyFunctionality is called instead of mockCheckMyFunctionality.
However, if I replace:
jest.mock('../modules/MyFunctionality', () => ({
checkMyFunctionality: mockCheckMyFunctionality
}));
With:
MyFunctionality.checkMyFunctionality = mockCheckMyFunctionality;
The test will pass, showing that mockCheckMyFunctionality is actually called. However, this is hacky and fails EsLint checking.
The method I am testing is just this:
setupMyFunctionality() {
checkMyFunctionality(this.props.something);
}
How can I modify the mocking such that it is visible inside App?
Reassignment seems to work but mocking doesn't.
Maybe you forgot to create a manual mock for the MyFuncionality module.
As in the documentation for mocking a module:
Manual mocks are defined by writing a module in a __mocks__/
subdirectory immediately adjacent to the module. For example, to mock a module called user in the models directory, create a file called user.js and put it in the models/__mocks__ directory.
...
When a manual mock exists for a given module, Jest's module system will use that module when explicitly calling jest.mock('moduleName').
If that's the case, you can create a folder named __mocks__ adjacent to the MyFunctionality file, then create a MyFunctionality file within that folder, containing the mock implementation as in the code snippet, and explicitly call the mock by calling jest.mock('../modules/MyFunctionality') before the tests.
Another solution, which I started using in my projects, is passing the dependencies as props to the components. To implement that you could change the App component and pass the MyFunctionality as props, thus in the above test you would need just to pass the mock implemented as props, avoiding the necessity of creating __mocks__/ folder to accomplish the test.

How does lazy module loading work in typescript?

In this typescript book section the author explains lazy loading with the following example:
import foo = require('foo');
export function loadFoo() {
// This is lazy loading `foo` and using the original module *only* as a type annotation
var _foo: typeof foo = require('foo');
// Now use `_foo` as a variable instead of `foo`.
}
According to the author typescript only loads the type of foo on the first call to require but on the second call when a foo var is created it loads the entire module required to create the var _foo.
How does this work. Can someone show a more detailed example of what's going on under the hood?
Typescript 2.4 now supports Dynamic Import Expressions where you can lazily import modules.
Here is the example:
async function getZipFile(name: string, files: File[]): Promise<File> {
const zipUtil = await import('./utils/create-zip-file');
const zipContents = await zipUtil.getContentAsBlob(files);
return new File(zipContents, name);
}
Behind the hood it is still using require as you can see in the transpiled code here
It's mentioned in the typescript handbook
The compiler detects whether each module is used in the emitted
JavaScript. If a module identifier is only ever used as part of a type
annotations and never as an expression, then no require call is
emitted for that module.
In this example, the first foo (the one without an underscore) is used only once in the type annotation as an argument of typeof, so the first require('foo') is omitted from the generated javascript code. You can check generated .js file to see that, and there would be only one call to the require at runtine, the 'second' one.
When loadFoo() is called, require('foo') is executed, calling built-in node.js require() function which loads foo module at runtime in the usual way.

Use function from the main.js in imported module

I'm trying to include IOUtil.js and ChannelReplacement.js in my add-on, using the Cu.import(...) function. These two both use xpcom_generateQI, which I'm trying to obtain from the XPCOM jsm, but the two scripts cant access it.
const {Cc, Ci, Cu, Cr} = require("chrome");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
const xpcom_generateQI = XPCOMUtils.generateQI;
Cu.import(self.data.url("IOUtil.js"));
Cu.import(self.data.url("ChannelReplacement.js"));
gives me xpcom_generateQI is not defined.
How do I access a function which is defined in main.js?
Issues
Don't use Cu.import for local SDK modules. Don't write JS code modules for SDK add-ons, the SDK uses CommonJS-style modules together with the require() facility which also comes with proper cleanup for free, which cannot be said for JS code modules and Cu.import (you'd need to properly Cu.unload everything and likely kill some references yourself).
That https-everywhere stuff are neither JS code modules nor SDK modules, but uses the subscript loader. Either convert it to SDK code modules, or use the subscript loader yourself.
It is OK to import built-in JS Code modules in different scopes/modules. There is not actually a need to make available xpcom_generateQI from main (although it can be done; well, get to that).
To be future proof, you should bind your xpcom_generateQI shortcut properly, as in XPCOMUtils.generateQI.bind(XPCOMUtils). Otherwise, if the implementation changes and requires a proper this, your stuff will break.
To export something from any CommonJS module, you need to put it into the exports module. See the first link.
To import something, use require() (first link again).
Be aware of circular references, where Module A imports Module B imports Module A. Right now this kinda works (but only kinda, because some stuff might not be available from Module A when Module B imports it like this, as Module A is not fully loaded). Better avoid it.
Example 1 (circular)
So here is a example with circular require (main imports modules imports main)
main.js
function someFunction() {
console.log("some function called");
}
exports.someFunction = someFunction;
var mod = require("./module");
mod.anotherFunction();
module.js
const { someFunction } = require("./main");
exports.anotherFunction = function() {
someFunction();
}
Now, because of circular references this is a fragile construct. If works right now, but when modules get more complex or the SDK changes, it might break... Better put someFunction into a third module.
Example 2 (avoiding circular imports)
main.js
var mod = require("./module");
mod.anotherFunction();
// Or call someFunction directly
var { someFunction } = require("./utils");
someFunction();
module.js
const { someFunction } = require("./utils");
exports.anotherFunction = function() {
someFunction();
}
utils.js
function someFunction() {
console.log("some function called");
}
exports.someFunction = someFunction;
There are no circles anymore. If you wanted to reuse xpcom_generateQI, you'd put it as a property of exports in utils.js (in this example) and later require it with require("./utils").
https-everywhere
The https-everywhere stuff needs to be either converted or loaded using the subscript loader. I would recommend against the subscript loader, because in all likelihood the verbatim https-everywhere code does not clean up after itself. I'd actually also recommend against just converting it by throwing some stuff in (exports.xzy = ...). This code is not meant to be run in the SDK. Better create your own implementation and just borrow ideas from https-everywhere where needed.

Categories