Jest mock class method on a test-by-test basis - javascript

I'm trying to mock out a utility library class with a method that returns a JSON.
Actual library structure
module.exports = class Common() {
getConfig() {
return {
real: 'data'
}
}
The file under test looks like:
const Common = require('./common');
const common = new Common();
const config = common.getConfig();
...
const someFunction = function() {
// config.real is used inside this function
}
I'm trying to mock out the Common class and return a different config JSON for each Jest test.
const fileUnderTest = require('./../fileUnderTest.js');
const Common = require('./../common.js');
jest.mock('./../common.js');
describe('something', () => {
it('test one', () => {
Common.getConfig = jest.fn().mockImplementation(() => {
return {
real : 'fake' // This should be returned for test one
};
});
fileUnderTest.someFunction(); //config.real is undefined at this point
});
it('test two', () => {
Common.getConfig = jest.fn().mockImplementation(() => {
return {
real : 'fake2' // This should be returned for test two
};
});
})
})
Is it possible to set the return value from the mock class method created by the automock of common.js at the top of the test file?
I've tried to use mockReturnValueOnce() etc.

jest.mock
In this case you don't really need to auto-mock the entire common module since you are just replacing the implementation of one method so jest.mock('./../common'); isn't necessary.
Common.getConfig
getConfig is a prototype method so getConfig exists on the prototype of Common. To mock it use Common.prototype.getConfig instead of Common.getConfig.
config in fileUnderTest.js
An instance of Common gets created and config gets set to the result of calling common.getConfig() as soon as fileUnderTest runs, which happens as soon as it gets required so the mock for Common.prototype.getConfig has to be in place before you call require('./../fileUnderTest').
const Common = require('./../common');
Common.prototype.getConfig = jest.fn().mockImplementation(() => ({ real: 'fake' }));
const fileUnderTest = require('./../fileUnderTest');
describe('something', () => {
it('should test something', () => {
fileUnderTest.someFunction(); // config.real is 'fake' at this point
});
});
Update
To mock config.real differently for each test for code like this requires that the modules be reset between tests:
describe('something', () => {
afterEach(() => {
jest.resetModules(); // reset modules after each test
})
it('test one', () => {
const Common = require('./../common');
Common.prototype.getConfig = jest.fn().mockImplementation(() => ({ real: 'fake' }));
const fileUnderTest = require('./../fileUnderTest');
fileUnderTest.someFunction(); // config.real is 'fake'
});
it('test two', () => {
const Common = require('./../common');
Common.prototype.getConfig = jest.fn().mockImplementation(() => ({ real: 'fake2' }));
const fileUnderTest = require('./../fileUnderTest');
fileUnderTest.someFunction(); // config.real is 'fake2'
})
})
Resetting the modules is necessary because once a module is required it is added to the module cache and that same module gets returned each time it is required unless the modules are reset.

Related

Jest: restore original module implementation on a manual mock

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"}]');
});

How To Reset Manual Mocks In Jest

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.

How to change mock implementation on a per single test basis?

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

How to change the behaviour of a mocked import?

I am quite confused with mocking in Jest an how to unit test the implementations. The thing is i want to mock different expected behaviours.
Is there any way to achieve this? as imports can be only on the top of the file and to be able to mock something it must be declared before the import. I have also tried to pass a local function so I could overwrite the behaviour but jest complains you are not allowed to pass anything local.
jest.mock('the-package-to-mock', () => ({
methodToMock: jest.fn(() => console.log('Hello'))
}));
import * as theThingToTest from '../../../app/actions/toTest'
import * as types from '../../../app/actions/types'
it('test1', () => {
expect(theThingToTest.someAction().type).toBe(types.SOME_TYPE)
})
it('test2', () => {
//the-package-to-mock.methodToMock should behave like something else
expect(theThingToTest.someAction().type).toBe(types.SOME_TYPE)
})
internally as you can imagine theThingToTest.someAction() uses the-package-to-mock.methodToMock
You can mock with a spy and import the mocked module. In your test you set how the mock should behave using mockImplementation:
jest.mock('the-package-to-mock', () => ({
methodToMock: jest.fn()
}));
import { methodToMock } from 'the-package-to-mock'
it('test1', () => {
methodToMock.mockImplementation(() => 'someValue')
})
it('test2', () => {
methodToMock.mockImplementation(() => 'anotherValue')
})
I use the following pattern:
'use strict'
const packageToMock = require('../path')
jest.mock('../path')
jest.mock('../../../../../../lib/dmp.db')
beforeEach(() => {
packageToMock.methodToMock.mockReset()
})
describe('test suite', () => {
test('test1', () => {
packageToMock.methodToMock.mockResolvedValue('some value')
expect(theThingToTest.someAction().type).toBe(types.SOME_TYPE)
})
test('test2', () => {
packageToMock.methodToMock.mockResolvedValue('another value')
expect(theThingToTest.someAction().type).toBe(types.OTHER_TYPE)
})
})
Explanation:
You mock the class you are trying to use on test suite level, make sure the mock is reset before each test and for every test you use mockResolveValue to describe what will be return when mock is returned
Another way is to use jest.doMock(moduleName, factory, options).
E.g.
the-package-to-mock.ts:
export function methodToMock() {
return 'real type';
}
toTest.ts:
import { methodToMock } from './the-package-to-mock';
export function someAction() {
return {
type: methodToMock(),
};
}
toTest.spec.ts:
describe('45006254', () => {
beforeEach(() => {
jest.resetModules();
});
it('test1', () => {
jest.doMock('./the-package-to-mock', () => ({
methodToMock: jest.fn(() => 'type A'),
}));
const theThingToTest = require('./toTest');
expect(theThingToTest.someAction().type).toBe('type A');
});
it('test2', () => {
jest.doMock('./the-package-to-mock', () => ({
methodToMock: jest.fn(() => 'type B'),
}));
const theThingToTest = require('./toTest');
expect(theThingToTest.someAction().type).toBe('type B');
});
});
unit test result:
PASS examples/45006254/toTest.spec.ts
45006254
✓ test1 (2016 ms)
✓ test2 (1 ms)
-----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-----------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
toTest.ts | 100 | 100 | 100 | 100 |
-----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 3.443 s
source code: https://github.com/mrdulin/jest-v26-codelab/tree/main/examples/45006254
spyOn worked best for us. See previous answer:
https://stackoverflow.com/a/54361996/1708297
How to Change Mocked Functions For Different Test Scenarios
In my scenario I tried to define the mock function outside of the jest.mock which will return an error about trying to access the variable before it's defined. This is because modern Jest will hoist jest.mock so that it can occur before imports. Unfortunately this leaves you with const and let not functioning as one would expect since the code hoists above your variable definition. Some folks say to use var instead as it would become hoisted, but most linters will yell at you, so as to avoid that hack this is what I came up with:
Jest Deferred Mocked Import Instance Calls Example
This allows us to handle cases like new S3Client() so that all new instances are mocked, but also while mocking out the implementation. You could likely use something like jest-mock-extended here to fully mock out the implementation if you wanted, rather than explicitly define the mock.
The Problem
This example will return the following error:
eferenceError: Cannot access 'getSignedUrlMock' before initialization
Test File
const sendMock = jest.fn()
const getSignedUrlMock = jest.fn().mockResolvedValue('signedUrl')
jest.mock('#aws-sdk/client-s3', () => {
return {
S3Client: jest.fn().mockImplementation(() => ({
send: sendMock.mockResolvedValue('file'),
})),
GetObjectCommand: jest.fn().mockImplementation(() => ({})),
}
})
jest.mock('#aws-sdk/s3-request-presigner', () => {
return {
getSignedUrl: getSignedUrlMock,
}
})
The Answer
You must defer the call in a callback like so:
getSignedUrl: jest.fn().mockImplementation(() => getSignedUrlMock())
Full Example
I don't want to leave anything up to the imagination, although I phaked the some-s3-consumer from the actual project, but it's not too far off.
Test File
import { GetObjectCommand, S3Client } from '#aws-sdk/client-s3'
import { SomeS3Consumer } from './some-s3-consumer'
const sendMock = jest.fn()
const getSignedUrlMock = jest.fn().mockResolvedValue('signedUrl')
jest.mock('#aws-sdk/client-s3', () => {
return {
S3Client: jest.fn().mockImplementation(() => ({
send: sendMock.mockResolvedValue('file'),
})),
GetObjectCommand: jest.fn().mockImplementation(() => ({})),
}
})
jest.mock('#aws-sdk/s3-request-presigner', () => {
return {
// This is weird due to hoisting shenanigans
getSignedUrl: jest.fn().mockImplementation(() => getSignedUrlMock()),
}
})
describe('S3Service', () => {
const service = new SomeS3Consumer()
describe('S3 Client Configuration', () => {
it('creates a new S3Client with expected region and credentials', () => {
expect(S3Client).toHaveBeenCalledWith({
region: 'AWS_REGION',
credentials: {
accessKeyId: 'AWS_ACCESS_KEY_ID',
secretAccessKey: 'AWS_SECRET_ACCESS_KEY',
},
})
})
})
describe('#fileExists', () => {
describe('file exists', () => {
it('returns true', () => {
expect(service.fileExists('bucket', 'key')).resolves.toBe(true)
})
it('calls S3Client.send with GetObjectCommand', async () => {
await service.fileExists('bucket', 'key')
expect(GetObjectCommand).toHaveBeenCalledWith({
Bucket: 'bucket',
Key: 'key',
})
})
})
describe('file does not exist', () => {
beforeEach(() => {
sendMock.mockRejectedValue(new Error('file does not exist'))
})
afterAll(() => {
sendMock.mockResolvedValue('file')
})
it('returns false', async () => {
const response = await service.fileExists('bucket', 'key')
expect(response).toBe(false)
})
})
})
describe('#getSignedUrl', () => {
it('calls GetObjectCommand with correct bucket and key', async () => {
await service.getSignedUrl('bucket', 'key')
expect(GetObjectCommand).toHaveBeenCalledWith({
Bucket: 'bucket',
Key: 'key',
})
})
describe('file exists', () => {
it('returns the signed url', async () => {
const response = await service.getSignedUrl('bucket', 'key')
expect(response).toEqual(ok('signedUrl'))
})
})
describe('file does not exist', () => {
beforeEach(() => {
getSignedUrlMock.mockRejectedValue('file does not exist')
})
afterAll(() => {
sendMock.mockResolvedValue('file')
})
it('returns an S3ErrorGettingSignedUrl with expected error message', async () => {
const response = await service.getSignedUrl('bucket', 'key')
expect(response.val).toStrictEqual(new S3ErrorGettingSignedUrl('file does not exist'))
})
})
})
})
Andreas answer work well with functions, here is what I figured out using it:
// You don't need to put import line after the mock.
import {supportWebGL2} from '../utils/supportWebGL';
// functions inside will be auto-mocked
jest.mock('../utils/supportWebGL');
const mocked_supportWebGL2 = supportWebGL2 as jest.MockedFunction<typeof supportWebGL2>;
// Make sure it return to default between tests.
beforeEach(() => {
// set the default
supportWebGL2.mockImplementation(() => true);
});
it('display help message if no webGL2 support', () => {
// only for one test
supportWebGL2.mockImplementation(() => false);
// ...
});
It won't work if your mocked module is not a function. I haven't been able to change the mock of an exported boolean for only one test :/. My advice, refactor to a function, or make another test file.
export const supportWebGL2 = /* () => */ !!window.WebGL2RenderingContext;
// This would give you: TypeError: mockImplementation is not a function

How to unit test/stub async call in redux

How is the proper way to unit test the following redux async action?
const client = contentful.createClient(clientConfig);
export const fetchNavigation = () => {
return dispatch => {
return client.getEntries({content_type: 'navigation'})
.then((entries) => {
console.log('All entries for content_type = navigation')
dispatch(receiveNavigation(entries))
})
.catch(error => {
console.log('Something went wrong');
dispatch(fetchNavigationFailure(error));
});
}
}
I don't know how to customise the web request response body performed by client.getEntries. I think that replacing the getEntries function with my own one would do the trick. However, I don't know where to start to do that.
Here is the unit test I wrote:
const middlewares = [ thunk ]
const mockStore = configureMockStore(middlewares)
describe('fetchNavigation', () => {
it('creates RECEIVE_NAVIGATION when fetching navigation is done', () => {
// Here I should prepare the client.getEntries() returned promise
const expectedBodyResponse = { includes: ['do something', 'yay!'] }
const expectedActions = [
{ type: actions.RECEIVE_NAVIGATION, navigation: expectedBodyResponse }
]
const store = mockStore({ todos: [] })
return store.dispatch(actions.fetchNavigation())
.then(() => {
expect(store.getActions()).toEqual(expectedActions)
})
})
})
IMO mocking getEntries (and probably createClient) seems to be the right way to do. :)
It depends how you load the contentful sdk. As I see you're using ES Modules and Jasmine, right?
To mock the getEntries function you have to mock the createClient as the client is not accessible from within your test.
I think this this answer might be what you're looking for.
I just wrote down an example.
import contentful from 'contentful';
export const fetchNavigation = () => {
return (dispatch) => {
return contentful.createClient({ accessToken: 'fooo', space: 'bar' })
.getEntries({ content_type: 'navigation' })
.then(() => {
dispatch('yeah');
})
.catch(error => console.error('Something went wrong', error));
};
};
import { fetchNavigation } from '../Action';
import * as contentful from 'contentful';
describe('Contentful mocking', () => {
it('should be possible to mock Contentful', (done) => {
const client = { getEntries: () => { return Promise.resolve(); } };
const spy = {
fn: (value) => {
expect(value).toBe('yeah');
done();
},
};
spyOn(contentful.default, 'createClient').and.returnValue(client);
fetchNavigation()(spy.fn);
});
});
I had to move the createClient call into the action itself, because otherwise I don't think it's possible to reach and mock it when it's hidden in the module scope. I then used the import * as contentful from 'contentful' to mock and overwrite the needed functionality and to have the flexibility to adjust everything to my needs.
The usage of the createClient feels a bit unfortunate for me. I'd probably restructure everything a bit and would pass the client as dependency of all the actions? This way the mocking would become way easier and when you also have several action modules, there is most probably no need to initialize the client several times?
I solved in this way.
First I moved the creation of the client to its own file with functions initClient() and getClient(). The module is called contentfulClient.
Then, I found out that it is possible to mock the function of an instantiated object in sinon:
import * as contentful from './services/contentfulClient';
const client = contentful.initClient(clientConfig);
const navigation = {
items: ['page1', 'page2']
};
// Returns a promise with navigation as content
sinon.stub(client, 'getEntries').resolves(navigation);
// Assert
return store.dispatch(actions.fetchNavigation())
.then( () => { expect(store.getActions()).toEqual(expectedActions)
})

Categories

Resources