Invalidate node cache when using Jest - javascript

I have a file with object that gets populated with process.env properties:
env.js
console.log('LOADING env.js');
const {
PROXY_PREFIX = '/api/',
USE_PROXY = 'true',
APP_PORT = '8080',
API_URL = 'https://api.address.com/',
NODE_ENV = 'production',
} = process.env;
const ENV = {
PROXY_PREFIX,
USE_PROXY,
APP_PORT,
API_URL,
NODE_ENV,
};
module.exports.ENV = ENV;
Now I try to test this file with different process.env properties:
env.test.js
const envFilePath = '../../config/env';
describe('environmental variables', () => {
const OLD_ENV = process.env;
beforeEach(() => {
process.env = { ...OLD_ENV };
delete process.env.NODE_ENV;
});
afterEach(() => {
process.env = OLD_ENV;
});
test('have default values', () => {
const { ENV } = require(envFilePath);
expect(ENV).toMatchSnapshot();
});
test('are string values (to avoid casting errors)', () => {
const { ENV } = require(envFilePath);
Object.values(ENV).forEach(val => expect(typeof val).toEqual('string'));
});
test('will receive process.env variables', () => {
process.env.NODE_ENV = 'dev';
process.env.PROXY_PREFIX = '/new-prefix/';
process.env.API_URL = 'https://new-api.com/';
process.env.APP_PORT = '7080';
process.env.USE_PROXY = 'false';
const { ENV } = require(envFilePath);
expect(ENV.NODE_ENV).toEqual('dev');
expect(ENV.PROXY_PREFIX).toEqual('/new-prefix/');
expect(ENV.API_URL).toEqual('https://new-api.com/');
expect(ENV.APP_PORT).toEqual('7080');
expect(ENV.USE_PROXY).toEqual('false');
});
});
Unfortunately, even though I try to load the file in every test separately the file gets loaded only once, making the third test fail with:
Expected value to equal:
"dev"
Received:
"production"
P.S. It doesn't fail when I run the test alone.
I also know that env.js loads only once because console.log('LOADING env.js'); gets fired only once.
I tried to invalidate Nodes cache like:
beforeEach(() => {
delete require.cache[require.resolve(envFilePath)];
process.env = { ...OLD_ENV };
delete process.env.NODE_ENV;
});
but require.cache is empty {} before each test so it seems that Jest is somehow responsible for importing the file.
I also tried to run yarn jest --no-cache but didn't help.
So what I want is to load env.js before each test so I can test how it behaves with different node environmental variables.
jest#^22.0.4

You can use jest.resetModules() in beforeEach method to reset the already required modules
beforeEach(() => {
jest.resetModules()
process.env = { ...OLD_ENV };
delete process.env.NODE_ENV;
});

Instead of resetting all modules, you can require modules in isolation by using jest.isolateModules(fn).
For example:
test('are string values (to avoid casting errors)', () => {
jest.isolateModules(() => {
const { ENV } = require(envFilePath);
Object.values(ENV).forEach(val => expect(typeof val).toEqual('string'));
});
});

jest version is: "jest": "^26.6.3". Other answers are correct but didn't explain the reason. The reason why delete require.cache[require.resolve(MODULE_PATH)] does NOT work is because jest doesn't implement the require.cache. See issue#5741.
We should use jest.resetModules() or jest.isolateModules(fn) instead.
E.g.
index.js:
module.exports = { name: 'main' };
index.test.js:
describe('71254501', () => {
let obj1, obj2;
beforeEach(() => {
// This will work
// jest.resetModules();
// This doesn't work
delete require.cache[require.resolve('.')];
});
test('should pass 1', () => {
obj1 = require('.');
obj1.name = 'b';
console.log(obj1);
});
test('should pass 2', () => {
obj2 = require('.');
console.log(obj2);
});
});
Test result:
PASS stackoverflow/71254501/index.test.js
71254501
✓ should pass 1 (13 ms)
✓ should pass 2 (1 ms)
console.log
{ name: 'b' }
at Object.<anonymous> (stackoverflow/71254501/index.test.js:10:13)
console.log
{ name: 'b' }
at Object.<anonymous> (stackoverflow/71254501/index.test.js:14:13)
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 0.606 s, estimated 8 s
As you can see, the require cache of module .is NOT deleted. When test case 1 mutates the obj, test case 2 still requires the same instance of the module which is not expected. We want a fresh module .

Related

How to preload a javascript dependency via Jest?

Is it possible to inject the dependency via jest or alternative without having to import 'lib' into the file itself?
Example file.js
// Note: lib is not defined in this file
export const getDep = (str) => {
lib('something');
return str;
}
Example jest.config.js
module.exports = {
globals: {
window: {
lib: () => {} // does not work
},
lib: () => {} // does not work
},
setupFiles: ['<rootDir>/jest/setupFiles.js'], // doesnt work either
// ...other stuff
}
Example jest/setupFiles.js
const lib = () => {};
console.log(lib);
Example file.test.js
import { getDep } from "file.js";
describe('Test injection of global dependency using jest.config', () => {
it("should not break test", () => {
const result = getDep('hello');
expect(result).toEqual('hello')
})
})
Also tried jest - setupFiles
Error ReferenceError: lib is not defined
1 |
2 | export const getDep = (str) => {
> 3 | lib('something');
| ^
4 | return str;
Ok thanks to #jonrsharpe for a partial successful solution.
I had to use jest setupFiles, but also I had to add the dependency to the window object.
window.lib = lib;
const lib = () => {};
window.lib = lib; // Added code here..
console.log(lib);

How to mock fs module together with unionfs?

I have written a test case that successfully load files into virtual FS, and at the same time mounted a virtual volume as below
describe("should work", () => {
const { vol } = require("memfs");
afterEach(() => vol.reset());
beforeEach(() => {
vol.mkdirSync(process.cwd(), { recursive: true });
jest.resetModules();
jest.resetAllMocks();
});
it("should be able to mock fs that being called in actual code", async () => {
jest.mock("fs", () => {
return ufs //
.use(jest.requireActual("fs"))
.use(createFsFromVolume(vol) as any);
});
jest.mock("fs/promises", () => {
return ufs //
.use(jest.requireActual("fs/promises"))
.use(createFsFromVolume(vol) as any);
});
const { createFsFromVolume } = require("memfs");
const { ufs } = require("unionfs");
const { countFile } = require("../src/ops/fs");
vol.fromJSON(
{
"./some/README.md": "1",
"./some/index.js": "2",
"./destination": null,
},
"/app"
);
const result = ufs.readdirSync(process.cwd());
const result2 = ufs.readdirSync("/app");
const result3 = await countFile("/app");
console.log({ result, result2, result3 });
});
});
By using ufs.readdirSync, I can access to virtual FS and indeed result giving me files that loaded from disc into virtual FS, result2 representing /app which is a new volume created from vol.fromJSON.
Now my problem is I am unable to get the result for result3, which is calling countFile method as below
import fsPromises from "fs/promises";
export const countFile = async (path: string) => {
const result = await fsPromises.readdir(path);
return result.length;
};
I'm getting error
Error: ENOENT: no such file or directory, scandir '/app'
which I think it's because countFile is accessing the actual FS instead of the virtual despite I've had jest.mock('fs/promises')?
Please if anyone can provide some lead?
This is the function you want to unit test.
//CommonJS version
const fsPromises = require('fs/promises');
const countFile = async (path) => {
const result = await fsPromises.readdir(path);
return result.length;
};
module.exports = {
countFile
}
Now, how you would normally go about this, is to mock fsPromises. In this example specifically readdir() since that is the function being used in countFile.
This is what we call: a stub.
A skeletal or special-purpose implementation of a software component, used to develop or test a component that calls or is otherwise dependent on it. It replaces a called component.
const {countFile} = require('./index');
const {readdir} = require("fs/promises");
jest.mock('fs/promises');
beforeEach(() => {
readdir.mockReset();
});
it("When testing countFile, given string, then return files", async () => {
const path = "/path/to/dir";
// vvvvvvv STUB HERE
readdir.mockResolvedValueOnce(["src", "node_modules", "package-lock.json" ,"package.json"]);
const res = await countFile(path);
expect(res).toBe(4);
})
You do this because you're unit testing. You don't want to be dependent on other functions because that fails to be a unit test and more integration test. Secondly, it's a third-party library, which is maintained/tested by someone else.
Here is where your scenario applies. From my perspective, your objective isn't to test countFile() rather, to test fsPromises and maybe test functionality to read virtual file-systems: unionfs. If so then, fsPromises doesn't need to really be mocked.

Jest with axios in window.api in Vue [duplicate]

I need to test a function which opens a new tab in the browser
openStatementsReport(contactIds) {
window.open(`a_url_${contactIds}`);
}
I would like to mock window's open function, so I can verify the correct URL is passed in to the open function.
Using Jest, I don't know how to mock window. I tried to set window.open with a mock function, but this way doesn't work. Below is the test case:
it('the correct URL is called', () => {
window.open = jest.fn();
statementService.openStatementsReport(111);
expect(window.open).toBeCalled();
});
But it gives me the error
expect(jest.fn())[.not].toBeCalled()
jest.fn() value must be a mock function or spy.
Received:
function: [Function anonymous]
What should I do to the test case?
The following method worked for me. This approach allowed me to test some code that should work both in the browser and in Node.js, as it allowed me to set window to undefined.
This was with Jest 24.8 (I believe):
let windowSpy;
beforeEach(() => {
windowSpy = jest.spyOn(window, "window", "get");
});
afterEach(() => {
windowSpy.mockRestore();
});
it('should return https://example.com', () => {
windowSpy.mockImplementation(() => ({
location: {
origin: "https://example.com"
}
}));
expect(window.location.origin).toEqual("https://example.com");
});
it('should be undefined.', () => {
windowSpy.mockImplementation(() => undefined);
expect(window).toBeUndefined();
});
Instead of window, use global:
it('the correct URL is called', () => {
global.open = jest.fn();
statementService.openStatementsReport(111);
expect(global.open).toBeCalled();
});
You could also try:
const open = jest.fn()
Object.defineProperty(window, 'open', open);
There are a couple of ways to mock globals in Jest:
Use the mockImplementation approach (the most Jest-like way), but it will work only for those variables which has some default implementation provided by jsdom. window.open is one of them:
test('it works', () => {
// Setup
const mockedOpen = jest.fn();
// Without making a copy, you will have a circular dependency problem
const originalWindow = { ...window };
const windowSpy = jest.spyOn(global, "window", "get");
windowSpy.mockImplementation(() => ({
...originalWindow, // In case you need other window properties to be in place
open: mockedOpen
}));
// Tests
statementService.openStatementsReport(111)
expect(mockedOpen).toBeCalled();
// Cleanup
windowSpy.mockRestore();
});
Assign the value directly to the global property. It is the most straightforward, but it may trigger error messages for some window variables, e.g. window.href.
test('it works', () => {
// Setup
const mockedOpen = jest.fn();
const originalOpen = window.open;
window.open = mockedOpen;
// Tests
statementService.openStatementsReport(111)
expect(mockedOpen).toBeCalled();
// Cleanup
window.open = originalOpen;
});
Don't use globals directly (requires a bit of refactoring)
Instead of using the global value directly, it might be cleaner to import it from another file, so mocking will became trivial with Jest.
File ./test.js
jest.mock('./fileWithGlobalValueExported.js');
import { windowOpen } from './fileWithGlobalValueExported.js';
import { statementService } from './testedFile.js';
// Tests
test('it works', () => {
statementService.openStatementsReport(111)
expect(windowOpen).toBeCalled();
});
File ./fileWithGlobalValueExported.js
export const windowOpen = window.open;
File ./testedFile.js
import { windowOpen } from './fileWithGlobalValueExported.js';
export const statementService = {
openStatementsReport(contactIds) {
windowOpen(`a_url_${contactIds}`);
}
}
I'm directly assigning jest.fn() to window.open.
window.open = jest.fn()
// ...code
expect(window.open).toHaveBeenCalledTimes(1)
expect(window.open).toHaveBeenCalledWith('/new-tab','_blank')
In my component I need access to window.location.search. This is what I did in the Jest test:
Object.defineProperty(global, "window", {
value: {
location: {
search: "test"
}
}
});
In case window properties must be different in different tests, we can put window mocking into a function, and make it writable in order to override for different tests:
function mockWindow(search, pathname) {
Object.defineProperty(global, "window", {
value: {
location: {
search,
pathname
}
},
writable: true
});
}
And reset after each test:
afterEach(() => {
delete global.window.location;
});
We can also define it using global in setupTests:
// File 'setupTests.js'
global.open = jest.fn()
And call it using global in the actual test:
// File 'yourtest.test.js'
it('the correct URL is called', () => {
statementService.openStatementsReport(111);
expect(global.open).toBeCalled();
});
I found an easy way to do it: delete and replace
describe('Test case', () => {
const { open } = window;
beforeAll(() => {
// Delete the existing
delete window.open;
// Replace with the custom value
window.open = jest.fn();
// Works for `location` too, eg:
// window.location = { origin: 'http://localhost:3100' };
});
afterAll(() => {
// Restore original
window.open = open;
});
it('correct url is called', () => {
statementService.openStatementsReport(111);
expect(window.open).toBeCalled(); // Happy happy, joy joy
});
});
The window object in Jest is self-mocking
One of the things unaddressed in other answers is a comment by the OP:
Using Jest, I don't know how to mock the window.
The window object is already mocked and can be referenced out of the box.
From the documentation:
Jest ships with jsdom which simulates a DOM environment as if you were in the browser. This means that every DOM API that we call can be observed in the same way it would be observed in a browser!
Example:
describe('i am a window', () => {
it('has a window object', () => {
expect(window).toBeTruthy(); // test will pass
});
});
You can try this:
import * as _Window from "jsdom/lib/jsdom/browser/Window";
window.open = jest.fn().mockImplementationOnce(() => {
return new _Window({ parsingMode: "html" });
});
it("correct url is called", () => {
statementService.openStatementsReport(111);
expect(window.open).toHaveBeenCalled();
});
If it's similar to the window location problem at window.location.href can't be changed in tests. #890, you could try (adjusted):
delete global.window.open;
global.window = Object.create(window);
global.window.open = jest.fn();
In your Jest configuration, add setupFilesAfterEnv: ["./setupTests.js"], create that file, and add the code you want to run before the tests:
// setupTests.js
window.crypto = {
.....
};
Reference: setupFilesAfterEnv [array]
Try simply:
let windowOpenSpy: jest.SpyInstance;
beforeEach(() => {
windowOpenSpy = jest.spyOn(window, 'open');
});
it('should open window with dashboard url', () => {
expect(windowOpenSpy).toBeCalledWith('your URL', '_blank');
});
I have a utility function which allows me to mock any method on the window like so:
function givenMockWindowMethods(methods: Partial<{ [key in keyof Window]: jest.Mock<any, any> }>): () => void {
const mocks = Object.values(methods);
Object.entries(methods).forEach(([key, value]) => {
Object.defineProperty(window, key, { value });
});
return (): void => mocks.forEach((mock) => mock?.mockClear());
}
So if I need to mock the open method (or anything really) on the window, I can do:
const cleanupMocks = givenMockWindowMethods({ open: jest.fn() });
// expect(...).toBe(...)
//at the end of the test, clean it up
cleanupMocks()
You can test it:
describe('TableItem Components', () => {
let open_url = ""
const { open } = window;
beforeAll(() => {
delete window.open;
window.open = (url) => { open_url = url };
});
afterAll(() => {
window.open = open;
});
test('string type', async () => {
wrapper.vm.openNewTab('http://example.com')
expect(open_url).toBe('http://example.com')
})
})
const windowSpy = jest.spyOn(iFrame, "contentWindow", "get");
windowSpy.mockImplementation(() => ({
location: {
origin: "https://test.com",
href: "href",
hash: "hash"
}
}));
I tried a similar test, and it worked with me...
My code:
export const Blah = () => {
const BLAH = 'https://www.google.com/'
const handleBlah = () => {
window.open(BLAH, '_blank')
}
return (
<button onClick={handleBlah}> BLAHBLAH </button>
)
}
My test using Jest:
it('should be able to render "BLAHBLAH " button ', () => {
window.open = jest.fn();
const BLAH = 'https://www.google.com/'
const { getByText } = render(<Blah/>) // Get text by my page Blah
const buttonGoToBlah = getByText('BLAHBLAH') // Get button by text
fireEvent.click(buttonGoToBlah) // Simulate the click event
expect(window.open).toHaveBeenCalledTimes(1) // Expect the window.open have to been called at least once.
expect(window.open).toHaveBeenCalledWith(BLAH, '_blank'); // And the page should be the same called in my BLAH page
})

How to clear a module mock between tests in same test suite in Jest?

I've mocked some nodejs modules (one of them, for example, is fs). I have them in a __mocks__ folder (same level als node_modules) folder and the module mocking works. However, whichever "between test clearing" option I use, the next test is not "sandboxed". What is going wrong here?
A very simplified example of the mocked fs module is:
// __mocks__/fs.js
module.exports = {
existsSync: jest.fn()
.mockReturnValueOnce(1)
.mockReturnValueOnce(2)
.mockReturnValueOnce(3)
}
I'm simply expecting that in every test, whenever init() is called (see below), existsSync starts again at value 1: the first value of jest.fn().mockReturnValue(). In the testfile I have the following structure:
// init.test.js
const init = require("../init");
const { existsSync } = require("fs");
jest.mock("fs");
describe("initializes script", () => {
afterEach(() => {
// see below!
});
test("it checks for a package.json in current directory", () => {
init();
});
test("it stops script if there's a package.json in dir", () => {
init(); // should be run in clean environment!
});
}
And once again very simplified, the init.js file
const { existsSync } = require("fs");
console.log("value of mocked response : ", existsSync())
I'm getting the following results for existsSync() after the first and second run ofinit() respectively when I run in afterEach():
jest.resetModules() : 1, 2
existsSync.mockReset(): 1, undefined
existsSync.mockClear(): 1, 2
existsSync.mockRestore(): 1, undefined
Somebody know what I'am doing wrong? How do I clear module mock between tests in the same suite? I'll glady clarify if necessary. Thanks!
Reset the modules and require them again for each test:
describe("initializes script", () => {
afterEach(() => {
jest.resetModules()
});
beforeEach(() => {
jest.mock("fs");
})
test("it checks for a package.json in current directory", () => {
const init = require("../init");
init();
});
test("it stops script if there's a package.json in dir", () => {
const init = require("../init");
init();
});
}
I had problems with the solution above. I managed to solve the issue with the next snippet.
afterEach(() => {
Object.keys(mockedModule).forEach(method => mockedModule[method].mockReset())
})
I would prefer to have a native method doing this though. Something like mockedModule.mockReset().
For local variables, the scope of declaration is important.
const mockFunc1 = jest.fn() // possibly bad mock reset/clear between tests
describe('useGetMetaData', () => {
const mockFunc2 = jest.fn() // good mock reset/clear between tests
afterEach(() => {/* reset/clear mocks */})
test.todo('implement tests here')
})

Test module.hot with Jest

I'm trying to get coverage to 100% for a module with hot module reloading setup.
In my module I have this:
// app.js
if (module && module.hot) module.hot.accept();
In the test file I am trying to do this
// app.test.js
it('should only call module.hot.accept() if hot is defined', () => {
const accept = jest.fn();
global.module = { hot: { accept } };
jest.resetModules();
require('./app');
expect(accept).toHaveBeenCalled();
}
);
But when I log out module in app.js it shows the require stuff but doesn't contain the hot method set by test.
If you have a variable referencing the module object, then you can inject a mock module object into that variable for the test. For instance, you could do the following:
// app.js
// ...
moduleHotAccept(module);
// ...
export function moduleHotAccept(mod) {
if (mod && mod.hot) {
mod.hot.accept();
}
}
Which can be tested like so:
// app.test.js
import { moduleHotAccept } from './app'
it('should only call hot.accept() if hot is defined', () => {
const accept = jest.fn();
const mockModule = { hot: { accept } };
moduleHotAccept(mockModule);
expect(accept).toHaveBeenCalled();
}
);
it('should not throw if module is undefined', () => {
expect(moduleHotAccept).not.toThrow();
}
);
it('should not throw if module.hot is undefined', () => {
expect(
() => moduleHotAccept({notHot: -273})
).not.toThrow();
}
);
I've needed it as well without the ability to pass it from outside.
My solution was to use a jest "transform" that allows me to modify a bit the code of the file that is using module.hot.
So in order to setup it you need to add:
// package.json
"transform": {
"file-to-transform.js": "<rootDir>/preprocessor.js"
//-------^ can be .* to catch all
//------------------------------------^ this is a path to the transformer
},
Inside preprocessor.js,
// preprocessor.js
module.exports = {
process(src, path) {
if( path.includes(... the path of the file that uses module.hot)) {
return src.replace('module.hot', 'global.module.hot');
}
return src;
},
};
That transformer will replace module.hot to global.module.hot, that means that you can control it value in the tests like so:
// some-test.spec.js
global.module = {
hot: {
accept: jest.fn,
},
};
Hope that it helps.

Categories

Resources