I'm trying to test a module that imports from another that has a weird export structure.
I'm using javascript with node, and jest for testing.
This is basically the code structure for the exports and the tests.
// weirdModule.js
module.exports = ({param1, param2}) = {
const weirdModuleFunction = async () => {
return await someIrrelevantFunction(param1, param2);
};
return async () => {
try {
return await weirdModuleFunction();
}
catch (e) {
throw e;
}
};
}
}
// testThisModule.js
const _weirdModuleFunction = require('weirdModule.js');
const testThisFunction = () => {
const weirdModuleFunction = _weirdModuleFunction({'abc',123});
let someDataIWantToBeMockedFromWeirdModule = await weirdModuleFunction();
const resultIWantToExpect = someDataIWantToBeMockedFromWeirdModule + ' someDataForExample';
return resultIWantToExpect;
};
module.exports = { testThisFunction }
//testThisModule.test.js
const { testThisFunction } = require('testThisModule.js');
//jest mocks to return different values for weirdModule.js
it('should return some value from weirdModule + someData',()=>{
//mockImplementation for weirdModule, maybe even mockImplementationOnce for successive testing
expect(testThisFunction()).toEqual('whatIexpect1 someDataForExample');
expect(testThisFunction()).toEqual('whatIexpectWithADifferentMockImplementation someDataForExample');
});
I'd like a solution that allows me to mock what weirdModule.js returns, and to also be able to mock it so it returns different values.
I have many tests for different branches, so I need to be able to change the mock return for weirdModule so I can have data that can be used across many tests, and some for some specific tests
I CANNOT change the structure of weirdModule (I didn't design it), and I CANNOT use anything other than jest, and I don't want to use manual mocks from __mocks__
I have managed to get it to work with only one return value, but I need to be able to change it to different values. It currently works like this:
jest.mock('weirdModule.js', () => () => {
return jest.fn(() => {
return 'whatIexpect1';
});
}
How can I achieve this? I've been scratching my head for days trying to have jest play nice and not throw 'weirdModuleFunction is not a function' when trying to mock different values for different tests. Any help is appreciated.
You need to capture weirdModule.js as a mock module in your test file. To do this, add these at the beginning of testThisModule.test.js
const weirdModule = require('weirdModule.js');
jest.mock('weirdModule.js');
Now you can mock the weird module however you want, like this:
it('should return some value from weirdModule + someData', async () => {
//mockImplementation for weirdModule, maybe even mockImplementationOnce for successive testing
weirdModule.mockImplementationOnce( () => {
return () => {
return 'whatIexpect1';
};
});
expect(await testThisFunction()).toEqual('whatIexpect1 someDataForExample');
weirdModule.mockImplementationOnce( () => {
return () => {
return 'whatIexpectWithADifferentMockImplementation';
};
});
expect(await testThisFunction()).toEqual('whatIexpectWithADifferentMockImplementation someDataForExample');
});
Hope this helps!
Related
I have a Jest test suite of integration tests using a suite-global mock for database access, where depending on the SQL I return different mock responses:
jest.mock('#my-org/our-mysql-wrapper', () => {
const query = jest.fn(async (sql, params) => {
if (sql === 'select foo from bar') {
return [];
} else if (sql === 'select baz') {
return [{ messageId: 3 }, { messageId: 4 }, { messageId: 5 }];
} else if (...) {
return ...;
} else {
console.log('UNEXPECTED QUERY SENT TO MOCK: ', sql, params);
return [];
}
});
const end = jest.fn(async () => true);
return jest.fn(() => ({ query, end }));
});
describe('suite', () => {
//tests here
});
This works great for positive tests, but where I'm getting frustrated with it is for negative tests. For example, in some cases if the DB were to return no results we might want to throw an error. In order to test that I need to have my mock behave differently for the same input. Whereas a typical positive test would not overwrite the db mock before running, the negative test needs to:
it('should throw and handle an error if the db returns no results for Widget lookup', async () => {
const mockDB = require('#my-org/our-mysql-wrapper')();
mockDB.query.mockImplementation(jest.fn(asnyc (sql, params) => {
if ( sql === 'select * from Widgets' ){
//this is the use-case that I want to override for this test
return [];
}else{
//...
}
}));
const someValue = await tool.doThing();
expect(buglogger).toHaveBeenCalled(); //actual test will be more specific...
//I tried plugging in mockRestore/mockClear/mockReset here
});
As written above this test actually passes, but it breaks tests that run after it because it doesn't clean up after itself. To the best of my understanding, this is what mockClear(), mockReset(), and mockRestore() are supposed to do in different variations; but I haven't been able to find a way to restore my mock to the original pre-override mocked implementation at the end of my test.
I've also used jest.spyOn() in some other cases, but that doesn't do what I'm after either. In this case, my test fails and the mock remains broken for other tests, too.
it('should throw and handle an error if the db returns no results for Widget lookup', async () => {
const mockDB = require('#my-org/our-mysql-wrapper')();
jest.spyOn(mockDb, 'query');
mockDB.query.mockImplementation(jest.fn(asnyc (sql, params) => {
if ( sql === 'select * from Widgets' ){
//this is the use-case that I want to override for this test
return [];
}else{
//...
}
}));
const someValue = await tool.doThing();
expect(buglogger).toHaveBeenCalled(); //actual test will be more specific...
mockDb.query.mockRestore();
});
I have also tried mockImplementationOnce() and that won't work for me because the query in question is not the first one that will be run. Using this method does clean up after itself automatically, but doesn't (can't, as far as I can tell) make my test pass because it cleans itself up after the first use before the query in question is called.
BUT since mockImplementationOnce can clean itself up in a way that restores the original mock, shouldn't there be some manual way to override an existing mock just for one test? That's what mockImplementationOnce is doing, isn't it? (cleaning up after first call not after 1 test; but it appears to be restoring the original mock...)
What am I doing wrong, here?
I think the problem is in both the scenarios, you're trying to assign the mock to the actual package you imported.
Did you try to change your mock setup like below:
const mockDB = require('#my-org/our-mysql-wrapper')();
const dbSpy = jest.spyOn(mockDB, 'query');
dbSpy.mockImplementation(jest.fn(asnyc (sql, params) => {
...
}));
dbSpy.mockClear();
dbSpy.mockRestore();
I've got a controller that calls two functions and in one test I would like to see if the child function is called and in my second test see if it returns the correct number.
// ./utilities/sayMyName.js
exports.sayMyName = (name) => name;
// ./utilities/doubleNum.js
exports.doubleNum = (num) => num * 2;
// ./utilities/index.js
const { doubleNum } = require("./doubleNum");
const { sayMyName } = require("./sayMyName");
module.exports = {
doubleNum,
sayMyName,
};
// myController.js
const { doubleNum, sayMyName } = require("./utilities");
exports.doubleMyNum = (num, name) => {
const myName = sayMyName(name);
return doubleNum(num);
};
// myController.test.js
const myController = require("./myController");
const utilities = require("./utilities");
jest.mock("./utilities");
describe("doubleNum", () => {
test("should call sayMyName", () => {
myController.doubleMyNum(2, "test name");
expect(utilities.sayMyName).toHaveBeenCalledWith("test name");
});
test("should double my number", () => {
const { doubleNum } = jest.requireActual("./utilities");
expect(myController.doubleMyNum(2, "test name")).toBe(4);
});
});
First test passes however it's the second one that fails because I originally mocked the utilities module, I followed the docs and using jest.requireActual should bring back the original function but it isn't. I did read that mapping the exports like I did in index.js and using deconstructing can cause issues with intercepting a function to mock it. How can I go about getting this to work?
SORTED
After much reading and testing I'd like to think I've sorted the issue by realising that I was trying to change the implementation of function from index.js that was already imported into the myController.js file that contained the function I was testing
So for example if I want to change the implementation of only one of the exported functions from my index.js file I need to mock the module and reimport myController.js for it to have an affect. So this works:
const myController = require("./myController");
const utilities = require("./utilities");
jest.mock("./utilities");
describe("doubleNum", () => {
beforeEach(() => {
jest.resetModules();
});
test("should call sayMyName", () => {
const spy = jest.spyOn(utilities, "sayMyName");
myController.doubleMyNum(2, "test name");
expect(spy).toHaveBeenCalledWith("test name");
});
test("should double my number", () => {
jest.mock("./utilities", () => {
const { sayMyName, doubleNum } = jest.requireActual("./utilities");
return {
sayMyName,
doubleNum,
};
});
// Reimport myController for the actual utilities module to be restored
const myController = require("./myController");
expect(myController.doubleMyNum(2, "test name")).toBe(4);
});
});
Of course this could be simplified had I not used deconstruction in myController.js, hopefully someone else finds this helpful.
I think this might be because you aren’t doing the mocking in a beforeEach or beforeAll block.
Alternatively, you can look at the resetModules example here, and mock or require the module within each test block instead of for all tests: jest.resetModules
I have a pretty common testing use case and I am not sure what's the best approach there.
Context
I would like to test a module that depends on a userland dependency. The userland dependency (neat-csv) exports a single function that returns a Promise.
Goal
I want to mock neat-csv's behavior so that it rejects with an error for one single test. Then I want to restore the original module implementation.
AFAIK, I can't use jest.spyOn here as the module exports a single function.
So I thought using manual mocks was appropriated and it works. However I can't figure it out how to restore the original implementation over a manual mock.
Simplified example
For simplicity here's a stripped down version of the module I am trying to test:
'use strict';
const neatCsv = require('neat-csv');
async function convertCsvToJson(apiResponse) {
try {
const result = await neatCsv(apiResponse.body, {
separator: ';'
});
return result;
} catch (parseError) {
throw parseError;
}
}
module.exports = {
convertCsvToJson
};
And here's an attempt of testing that fails on the second test (non mocked version):
'use strict';
let neatCsv = require('neat-csv');
let { convertCsvToJson } = require('./module-under-test.js');
jest.mock('neat-csv', () =>
jest.fn().mockRejectedValueOnce(new Error('Error while parsing'))
);
const csv = 'type;part\nunicorn;horn\nrainbow;pink';
const apiResponse = {
body: csv
};
const rejectionOf = (promise) =>
promise.then(
(value) => {
throw value;
},
(reason) => reason
);
test('mocked version', async () => {
const e = await rejectionOf(convertCsvToJson(apiResponse));
expect(neatCsv).toHaveBeenCalledTimes(1);
expect(e.message).toEqual('Error while parsing');
});
test('non mocked version', async () => {
jest.resetModules();
neatCsv = require('neat-csv');
({ convertCsvToJson } = require('./module-under-test.js'));
const result = await convertCsvToJson(apiResponse);
expect(JSON.stringify(result)).toEqual(
'[{"type":"unicorn","part":"horn"},{"type":"rainbow","part":"pink"}]'
);
});
I am wondering if jest is designed to do such things or if I am going the wrong way and should inject neat-csv instead ?
What would be the idiomatic way of handling this ?
Yes, Jest is designed to do such things.
The API method you are looking for is jest.doMock. It provides a way of mocking modules without the implicit hoisting that happens with jest.mock, allowing you to mock in the scope of tests.
Here is a working example of your test code that shows this:
const csv = 'type;part\nunicorn;horn\nrainbow;pink';
const apiResponse = {
body: csv
};
const rejectionOf = promise =>
promise.then(value => {
throw value;
}, reason => reason);
test('mocked version', async () => {
jest.doMock('neat-csv', () => jest.fn().mockRejectedValueOnce(new Error('Error while parsing')));
const neatCsv = require('neat-csv');
const { convertCsvToJson } = require('./module-under-test.js');
const e = await rejectionOf(convertCsvToJson(apiResponse));
expect(neatCsv).toHaveBeenCalledTimes(1);
expect(e.message).toEqual('Error while parsing');
jest.restoreAllMocks();
});
test('non mocked version', async () => {
const { convertCsvToJson } = require('./module-under-test.js');
const result = await convertCsvToJson(apiResponse);
expect(JSON.stringify(result)).toEqual('[{"type":"unicorn","part":"horn"},{"type":"rainbow","part":"pink"}]');
});
I have a manual mock of crypto that looks like this:
// __mocks__/crypto.js
const crypto = jest.genMockFromModule('crypto')
const toString: Function = jest.fn(() => {
return {}.toString()
})
const mockStringable = {toString}
const update: Function = jest.fn(() => mockStringable)
const deciper = {update}
crypto.createDecipheriv = jest.fn(() => deciper)
export default crypto
Which is basically tested like this:
const crypto = require('crypto')
jest.mock('crypto')
describe('cookie-parser', () => {
afterEach(() => {
jest.resetAllMocks()
})
describe('decryptCookieValue', () => {
it('should call the crypto library correctly', () => {
const result = decryptCookieValue('test-encryption-key', 'test-encrypted-value')
expect(crypto.pbkdf2Sync).toHaveBeenCalledTimes(2)
expect(crypto.createDecipheriv).toHaveBeenCalled()
// more tests, etc, etc, etc
expect(crypto.createDecipheriv('', '', '').update).toHaveBeenCalled()
expect(result).toEqual({}.toString())
})
})
...
This works however if in that same test file, I test another method that invokes decryptCookieValue from within crypto.createDecipheriv no longer returns my mock decipher. Instead it returns undefined. For instance:
describe('cookie-parser', () => {
afterEach(() => {
jest.resetAllMocks()
})
describe('decryptCookieValue', () => {
it('should call the crypto library correctly', () => {
const result = decryptCookieValue('test-encryption-key', 'test-encrypted-value')
expect(crypto.pbkdf2Sync).toHaveBeenCalledTimes(2)
expect(crypto.createDecipheriv).toHaveBeenCalled()
expect(crypto.createDecipheriv('', '', '').update).toHaveBeenCalled()
expect(result).toEqual({}.toString())
})
})
...
...
describe('parseAuthenticationCookie', () => {
it('should create the correct object', () => {
// parseAuthenticationCookie calls decryptCookieValue internally
const result = parseAuthenticationCookie('', '') // Fails because internal call to crypto.createDecipheriv stops returning mock decipher.
expect(result).toEqual({accessToken: null})
})
})
})
I think this is an issue with resetting the manual mock because if I take that later test and move it into a file all by itself with the same surrounding test harness it works just fine.
// new test file
import crypto from 'crypto'
import { parseAuthenticationCookie } from './index'
jest.mock('crypto')
describe('cookie-parser', () => {
afterEach(() => {
jest.resetAllMocks()
})
describe('parseAuthenticationCookie', () => {
it('should create the correct object', () => {
// Works just fine now
const result = parseAuthenticationCookie('', '')
expect(result).toEqual({accessToken: null})
})
})
})
Is my assessment here correct and, if so, how do I reset the state of the manual mock after each test?
From Jest docs:
Does everything that mockFn.mockClear() does, and also removes any mocked return values or implementations.
ref: https://jestjs.io/docs/en/mock-function-api#mockfnmockreset
In your example you are assuming that calling resetAllMocks will set your manual mock back and it's not.
The reason why your test works in a separate file is because jest runs each file isolated, which is nice since you can screw up only the specs living in the same file.
In your particular case something that might work is calling jest.clearAllMocks() (since this will keep the implementation and returned values).
clearMocks options is also available at the jest config object (false as default), if you want to clear all your mocks on every test, this might be handy.
Hope this help you or anyone else having having a similar issue.
Bonus tip (no quite related) If you are mocking a module that it's being used internally by other module and in some specific test you want to mock that module again with a different mock, make sure to require the module that it's using the mocked module internally again in that specific test, otherwise that module will still reference the mock you specified next to the imports statements.
Looks like the better way to test this is something on the lines of:
jest.mock('crypto')
describe('decrypt()', () => {
afterEach(() => {
jest.resetAllMocks()
})
it('returns value', () => {
const crypto = require('crypto')
const encryptedValue = 'encrypted-value'
const update = jest.fn()
const pbkdf2SyncResult = 'test result'
crypto.pbkdf2Sync = jest.fn().mockImplementation(() => {
return pbkdf2SyncResult
})
crypto.createDecipheriv = jest.fn().mockImplementation((format, key, iv) => {
expect(format).toEqual('aes-256-cbc')
expect(key).toEqual(pbkdf2SyncResult)
expect(iv).toEqual(pbkdf2SyncResult)
return {update}
})
decrypt(encryptedValue)
const inputBuffer = Buffer.from(encryptedValue, 'base64')
expect(update).toHaveBeenCalledWith(inputBuffer)
})
})
This way I don't even have to have the manual mock and I can use mockImplementationOnce if I need to have the mock reset.
I'd like to change the implementation of a mocked dependency on a per single test basis by extending the default mock's behaviour and reverting it back to the original implementation when the next test executes.
More briefly, this is what I'm trying to achieve:
Mock dependency
Change/extend mock implementation in a single test
Revert back to original mock when next test executes
I'm currently using Jest v21. Here is what a typical test would look like:
// __mocks__/myModule.js
const myMockedModule = jest.genMockFromModule('../myModule');
myMockedModule.a = jest.fn(() => true);
myMockedModule.b = jest.fn(() => true);
export default myMockedModule;
// __tests__/myTest.js
import myMockedModule from '../myModule';
// Mock myModule
jest.mock('../myModule');
beforeEach(() => {
jest.clearAllMocks();
});
describe('MyTest', () => {
it('should test with default mock', () => {
myMockedModule.a(); // === true
myMockedModule.b(); // === true
});
it('should override myMockedModule.b mock result (and leave the other methods untouched)', () => {
// Extend change mock
myMockedModule.a(); // === true
myMockedModule.b(); // === 'overridden'
// Restore mock to original implementation with no side effects
});
it('should revert back to default myMockedModule mock', () => {
myMockedModule.a(); // === true
myMockedModule.b(); // === true
});
});
Here is what I've tried so far:
mockFn.mockImplementationOnce(fn)
it('should override myModule.b mock result (and leave the other methods untouched)', () => {
myMockedModule.b.mockImplementationOnce(() => 'overridden');
myModule.a(); // === true
myModule.b(); // === 'overridden'
});
Pros
Reverts back to original implementation after first call
Cons
It breaks if the test calls b multiple times
It doesn't revert to original implementation until b is not called (leaking out in the next test)
jest.doMock(moduleName, factory, options)
it('should override myModule.b mock result (and leave the other methods untouched)', () => {
jest.doMock('../myModule', () => {
return {
a: jest.fn(() => true,
b: jest.fn(() => 'overridden',
}
});
myModule.a(); // === true
myModule.b(); // === 'overridden'
});
Pros
Explicitly re-mocks on every test
Cons
Cannot define default mock implementation for all tests
Cannot extend default implementation forcing to re-declare each mocked method
Manual mocking with setter methods (as explained here)
// __mocks__/myModule.js
const myMockedModule = jest.genMockFromModule('../myModule');
let a = true;
let b = true;
myMockedModule.a = jest.fn(() => a);
myMockedModule.b = jest.fn(() => b);
myMockedModule.__setA = (value) => { a = value };
myMockedModule.__setB = (value) => { b = value };
myMockedModule.__reset = () => {
a = true;
b = true;
};
export default myMockedModule;
// __tests__/myTest.js
it('should override myModule.b mock result (and leave the other methods untouched)', () => {
myModule.__setB('overridden');
myModule.a(); // === true
myModule.b(); // === 'overridden'
myModule.__reset();
});
Pros
Full control over mocked results
Cons
Lot of boilerplate code
Hard to maintain on long term
jest.spyOn(object, methodName)
beforeEach(() => {
jest.clearAllMocks();
jest.restoreAllMocks();
});
// Mock myModule
jest.mock('../myModule');
it('should override myModule.b mock result (and leave the other methods untouched)', () => {
const spy = jest.spyOn(myMockedModule, 'b').mockImplementation(() => 'overridden');
myMockedModule.a(); // === true
myMockedModule.b(); // === 'overridden'
// How to get back to original mocked value?
});
Cons
I can't revert mockImplementation back to the original mocked return value, therefore affecting the next tests
Use mockFn.mockImplementation(fn).
import { funcToMock } from './somewhere';
jest.mock('./somewhere');
beforeEach(() => {
funcToMock.mockImplementation(() => { /* default implementation */ });
// (funcToMock as jest.Mock)... in TS
});
test('case that needs a different implementation of funcToMock', () => {
funcToMock.mockImplementation(() => { /* implementation specific to this test */ });
// (funcToMock as jest.Mock)... in TS
// ...
});
A nice pattern for writing tests is to create a setup factory function that returns the data you need for testing the current module.
Below is some sample code following your second example although allows the provision of default and override values in a reusable way.
const spyReturns = returnValue => jest.fn(() => returnValue);
describe("scenario", () => {
beforeEach(() => {
jest.resetModules();
});
const setup = (mockOverrides) => {
const mockedFunctions = {
a: spyReturns(true),
b: spyReturns(true),
...mockOverrides
}
jest.doMock('../myModule', () => mockedFunctions)
return {
mockedModule: require('../myModule')
}
}
it("should return true for module a", () => {
const { mockedModule } = setup();
expect(mockedModule.a()).toEqual(true)
});
it("should return override for module a", () => {
const EXPECTED_VALUE = "override"
const { mockedModule } = setup({ a: spyReturns(EXPECTED_VALUE)});
expect(mockedModule.a()).toEqual(EXPECTED_VALUE)
});
});
It's important to say that you must reset modules that have been cached using jest.resetModules(). This can be done in beforeEach or a similar teardown function.
See jest object documentation for more info: https://jestjs.io/docs/jest-object.
Little late to the party, but if someone else is having issues with this.
We use TypeScript, ES6 and babel for react-native development.
We usually mock external NPM modules in the root __mocks__ directory.
I wanted to override a specific function of a module in the Auth class of aws-amplify for a specific test.
import { Auth } from 'aws-amplify';
import GetJwtToken from './GetJwtToken';
...
it('When idToken should return "123"', async () => {
const spy = jest.spyOn(Auth, 'currentSession').mockImplementation(() => ({
getIdToken: () => ({
getJwtToken: () => '123',
}),
}));
const result = await GetJwtToken();
expect(result).toBe('123');
spy.mockRestore();
});
Gist:
https://gist.github.com/thomashagstrom/e5bffe6c3e3acec592201b6892226af2
Tutorial:
https://medium.com/p/b4ac52a005d#19c5
When mocking a single method (when it's required to leave the rest of a class/module implementation intact) I discovered the following approach to be helpful to reset any implementation tweaks from individual tests.
I found this approach to be the concisest one, with no need to jest.mock something at the beginning of the file etc. You need just the code you see below to mock MyClass.methodName. Another advantage is that by default spyOn keeps the original method implementation but also saves all the stats (# of calls, arguments, results etc.) to test against, and keeping the default implementation is a must in some cases. So you have the flexibility to keep the default implementation or to change it with a simple addition of .mockImplementation as mentioned in the code below.
The code is in Typescript with comments highlighting the difference for JS (the difference is in one line, to be precise). Tested with Jest 26.6.
describe('test set', () => {
let mockedFn: jest.SpyInstance<void>; // void is the return value of the mocked function, change as necessary
// For plain JS use just: let mockedFn;
beforeEach(() => {
mockedFn = jest.spyOn(MyClass.prototype, 'methodName');
// Use the following instead if you need not to just spy but also to replace the default method implementation:
// mockedFn = jest.spyOn(MyClass.prototype, 'methodName').mockImplementation(() => {/*custom implementation*/});
});
afterEach(() => {
// Reset to the original method implementation (non-mocked) and clear all the mock data
mockedFn.mockRestore();
});
it('does first thing', () => {
/* Test with the default mock implementation */
});
it('does second thing', () => {
mockedFn.mockImplementation(() => {/*custom implementation just for this test*/});
/* Test utilising this custom mock implementation. It is reset after the test. */
});
it('does third thing', () => {
/* Another test with the default mock implementation */
});
});
I did not manage to define the mock inside the test itself so I discover that I could mock several results for the same service mock like this :
jest.mock("#/services/ApiService", () => {
return {
apiService: {
get: jest.fn()
.mockResolvedValueOnce({response: {value:"Value", label:"Test"}})
.mockResolvedValueOnce(null),
}
};
});
I hope it'll help someone :)
It's a very cool way I've discovered on this blog https://mikeborozdin.com/post/changing-jest-mocks-between-tests/
import { sayHello } from './say-hello';
import * as config from './config';
jest.mock('./config', () => ({
__esModule: true,
CAPITALIZE: null
}));
describe('say-hello', () => {
test('Capitalizes name if config requires that', () => {
config.CAPITALIZE = true;
expect(sayHello('john')).toBe('Hi, John');
});
test('does not capitalize name if config does not require that', () => {
config.CAPITALIZE = false;
expect(sayHello('john')).toBe('Hi, john');
});
});