Can I put my jest mock in a separate file? manual mock - javascript

In my get-sites.spec.js I created a mock like this which works perfectly fine:
jest.mock('#aws-sdk/client-secrets-manager', () => {
const SecretsManagerClient = jest.fn().mockImplementation(() => {
const result = {
SecretString: "{\"server\":\"host\",\"database\":\"database\",\"user\":\"userName\",\"password\":\"password\"}"
};
return {
send: jest.fn().mockResolvedValue(result)
};
});
const GetSecretValueCommand = jest.fn().mockImplementation(() => {
return {}
});
return {
SecretsManagerClient,
GetSecretValueCommand
}
});
I've tried creating the following directory structure and moved the mock code to the client-secrets-manager.js file.
__tests__/
|- get-sites.spec.js
__mocks__/
|- #aws-sdk/
|-- client-secrets-manager.js
src/
|- get-sites.js
Then in my get-sites.spec.js file I changed the mock code to jest.mock('#aws-sdk/client-secrets-manager');
When I run the test I get an error: TypeError: Cannot read properties of undefined (reading 'SecretString'). Is there some way to move my mock to a separate file to make it available to all my unit tests that will preserve the functionality?

Turns out I was close. I changed the client-secrets-manager.js file in the mocks folder to look like this:
const SecretsManagerClient = jest.fn().mockImplementation(() => {
return {
send: jest.fn()
};
});
const GetSecretValueCommand = jest.fn().mockImplementation(() => {
return {}
});
module.exports = {
SecretsManagerClient,
GetSecretValueCommand
}
Then in my get-sites.spec.js file I added this line to the test:
const sendResponse = {
SecretString: "{\"server\":\"host\",\"database\":\"database\",\"user\":\"userName\",\"password\":\"password\"}"
};
jest.spyOn(SecretsManagerClient.prototype, 'send').mockResolvedValue(sendResponse);
Then I was able to assert it was called like this:
expect(SecretsManagerClient).toHaveBeenCalledTimes(1);
expect(SecretsManagerClient.prototype.send.mock.calls.length).toEqual(1);

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);

Proxyquire shows error "Cannot find module"

I'm trying to use proxyquire to replace a private function for testing in my Meteor app.
Meteor 1.6.1
meteortesting:mocha#1.1.2
In my parentFunction.js:
import { some function } from 'anotherFile';
function childFunction() {
...
return someValue;
}
export default function parentFunction() {
return childFunction()
}
In my test file:
const proxyquire = require('proxyquire');
if (Meteor.isServer) {
...
describe('parentFunction', () => {
it('uses the mocked child function', () => {
const testThing = proxyquire('./parentFunction', {
'childFunction': () => ({ 'name': 'bob' }),
});
});
}
parentFunction.js is in the same folder as my test file, and just to double check the path, I made sure this works:
import parentFunction from './parentFunction';
But when I run the test, I'm seeing an error:
Error: Cannot find module './parentFunction.js'
What am I doing wrong? I've tried an absolute path, that didn't work. And as far as I can see from the documentation a relative path in the file where proxiquire is required, should be fine.
Thanks for any help!

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')
})

Jest URL.createObjectURL is not a function

I'm developping a reactJs application. I'm using jest to test my application.
I want to test a function that download a blob.
But unfortunately I receve this error:
URL.createObjectURL is not a function
my test function:
describe('download', () => {
const documentIntial = { content: 'aaa' };
it('msSaveOrOpenBlob should not have been called when navigao is undefined', () => {
window.navigator.msSaveOrOpenBlob = null;
download(documentIntial);
expect(window.navigator.msSaveOrOpenBlob).toHaveBeenCalledTimes(0);
});
});
The function I want to test:
export const download = document => {
const blob = new Blob([base64ToArrayBuffer(document.content)], {
type: 'application/pdf',
});
if (window.navigator && window.navigator.msSaveOrOpenBlob) {
window.navigator.msSaveOrOpenBlob(blob);
return;
}
const fileURL = URL.createObjectURL(blob);
window.open(fileURL);
};
This would appear to be as simple as setting up URL on the Global in Jest. Something like
describe('download', () => {
const documentIntial = { content: 'aaa' };
global.URL.createObjectURL = jest.fn();
it('msSaveOrOpenBlob should not have been called when navigao is undefined', () => {
global.URL.createObjectURL = jest.fn(() => 'details');
window.navigator.msSaveOrOpenBlob = jest.fn(() => 'details');
download(documentIntial);
expect(window.navigator.msSaveOrOpenBlob).toHaveBeenCalledTimes(1);
});
});
This should result in a test that you can also use for checking if global.URL.createObjectURL was called. As a side note: you may also run into a similar issue with window.open I would suggest mocking that as well if this becomes the case.
Since window.URL.createObjectURL is not (yet) available in jest-dom, you need to provide a mock implementation for it.
Don't forget to reset the mock implementation after each test.
describe("your test suite", () => {
window.URL.createObjectURL = jest.fn();
afterEach(() => {
window.URL.createObjectURL.mockReset();
});
it("your test case", () => {
expect(true).toBeTruthy();
});
});
jsdom, the JavaScript implementation of the WHATWG DOM used by jest doesn't implement this method yet.
You can find an open ticket about this exact issue on their github page where some workarounds are provided in comments. But if you need the blobURL to actually work you'll have to wait this FR is solved.
Workaround proposed in the comments of the issue for jest:
function noOp () { }
if (typeof window.URL.createObjectURL === 'undefined') {
Object.defineProperty(window.URL, 'createObjectURL', { value: noOp})
}
You just have to Write this in your setupTest.js
window.URL.createObjectURL = function() {};
The package jsdom-worker happens to provide this method, as well as adding support for web workers. The following worked for me:
npm install -D jsdom-worker
Then in package.json, edit or add a jest key:
{
...
"jest": {
"setupFiles": [
"jsdom-worker"
]
}
}
Just mocking the function global.URL.createObjectURL did not work for me, because the function was used by some modules during import and I got the error Jest URL.createObjectURL is not a function during import.
Instead it did help to create a file mockJsdom.js
Object.defineProperty(URL, 'createObjectURL', {
writable: true,
value: jest.fn()
})
Then import this file as the first import in your file containing the test
import './mockJsdom'
import { MyObjects} from '../../src/lib/mylib'
test('my test', () => {
// test code
}
Found here: https://jestjs.io/docs/manual-mocks#mocking-methods-which-are-not-implemented-in-jsdom

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