How can I mock a class using jest? - javascript

How can I mock something to test something like the following codes. I tried to follow this official doc, but still not working for me https://jestjs.io/docs/es6-class-mocks#calling-jestmock-with-the-module-factory-parameter
// somefile.ts
export const myPublish = async (event: any, context: any): Promise<any> => {
const myExportHelper = await ExportHelper.getInstance({
...commonProps,
});
// just some other stuff
// just some other stuff
await myExportHelper.transfer(arg1, arg2);
};
export class ExportHelper {
constructor(
private readonly bucket: string,
private readonly read: AWS.S3,
private readonly write: AWS.S3
) {}
static async getInstance(props: {
param1: string;
}) {
...
...
return new ExportHelper(arg1, arg2, arg3);
};
async transfer(param1, param2) {
...
...
console.log('bla bla bla');
}
}
// testfile.test.ts
import { myPublish, ExportHelper } from '../somefile';
beforeEach(() => {
});
afterEach(() => {
jest.clearAllMocks();
jest.resetAllMocks();
});
describe('myTest', () => {
it('should run successfully', async () => {
// Arrange
const eventMock = {
Records: [
{
...
}
]
}
jest.mock('../somefile');
const mockActualExportHelper = jest.requireActual('../somefile').ExportHelper;
const mockGetInstanceImpl = () => {};
// this says cannot read property instances of undefined
const mockExportHelper = mockActualExportHelper.mock.instances[0];
mockExportHelper.getInstance.mockImplementation(mockGetInstanceImpl);
mockExportHelper.transfer.mockImplementation(mockGetInstanceImpl);
// Act
await myPublish(eventMock, jasmine.any({}));
// Assert
expect(ExportHelper.getInstance).toBeCalled();
expect(ExportHelper.transfer).toBeCalled(); // also not sure if this is valid to use ExportHelper
});
});

I think what you're looking for is not a mock. If you want to spy what functions are called, you will need to use the spyOn. In jest you can do the following:
jest.spyOn(MyClass, 'myMethod');
And you can also mock the implementation to subtitute the default behavior of a method, a generalist example can be like this:
jest.spyOn(MyClass, 'myMethod').mockImplementation(jest.fn());
With that said, I would rewrite the test to spy the methods from ExportHelper and avoid external calls:
import {ExportHelper, myPublish} from '../app';
beforeEach(() => {
});
afterEach(() => {
jest.clearAllMocks();
jest.resetAllMocks();
});
describe('myTest', () => {
it('should run successfully', async () => {
// Arrange
const eventMock = {
Records: [
{
// ...
}
]
}
jest.spyOn(ExportHelper, 'getInstance').mockImplementation(jest.fn());
jest.spyOn(ExportHelper, 'transfer').mockImplementation(jest.fn());
// Act
await myPublish('arg1', 'arg2');
// Assert
expect(ExportHelper.getInstance).toBeCalled();
expect(ExportHelper.transfer).toBeCalled();
});
});
I just replaced this piece of code:
jest.mock('../somefile');
const mockActualExportHelper = jest.requireActual('../somefile').ExportHelper;
const mockGetInstanceImpl = () => {};
// this says cannot read property instances of undefined
const mockExportHelper = mockActualExportHelper.mock.instances[0];
mockExportHelper.getInstance.mockImplementation(mockGetInstanceImpl);
mockExportHelper.transfer.mockImplementation(mockGetInstanceImpl);
with
jest.spyOn(ExportHelper, 'getInstance').mockImplementation(jest.fn());
jest.spyOn(ExportHelper, 'transfer').mockImplementation(jest.fn());
Because jest will track down and watch any of these method's calls and then we can use jest's matchers to test if both of them were called. And the mockImplementation will isolate any further calls to be maded.
One thing that I noticed while reproducing your example, is that the transfer method is not being treated as a method when you get the instance from getInstance and therefore, the tests will not pass. But I think this question is not in the scope of the topic. Dunno if just happens to me.

Related

Javscript Jest test framework: when is a mock unmocked?

I want to mock a module and a function for a specific test. I have the following:
test("my test", () => {
jest.mock("some_module")
some_module.some_function = jest.fn();
...
})
test("another test", () => {
...
});
My question is, when the test is finished, will both mocks be "unset" so that I can use the real implementation for my next test? Or do I have to explicitly remove all mocks myself?
when the test is finished will all of the mocks be "unset"?
Jest tests are sandboxed on a test-file basis, so ordinarily after all tests in that file have been run, all the mocks would be restored.
However, what you are doing there: some_module.some_function = jest.fn(); is not mocking via Jest's mocking mechanism, it is monkey-patching an imported function. This will not be removed by Jest.
You should be doing something like this instead:
import { some_function } from 'some-module-path';
jest.mock('some-module-path');
test('my test', () => {
...
expect(some_function).toHaveBeenCalled(); // e.g.
}
UPDATE:
After the discussion in comments, here is an example of doing a simple monkey-patch safely in Jest, so that it is restored for subsequent tests in the same file:
// foo.js -----------------------------------
export const foo = () => 'real foo';
// bar.js -----------------------------------
import { foo } from './foo';
export const bar = () => foo();
// bar.test.js ------------------------------
import { bar } from './bar';
import * as fooModule from './foo';
describe('with mocked foo', () => {
let originalFoo;
beforeAll(() => {
// patch it!
originalFoo = fooModule.foo;
fooModule.foo = () => 'mocked foo';
});
afterAll(() => {
// put it back again!
fooModule.foo = originalFoo;
});
test('mocked return value from foo()', () => {
expect(bar()).toEqual('mocked foo');
});
});
describe('with real foo', () => {
test('expect real return value from foo()', () => {
expect(bar()).toEqual('real foo');
});
});
UPDATE 2:
Another alternative, you can mock the dependency and use the original implementation temporarily with jest.requireActual:
import { bar } from './bar';
import { foo } from './foo';
jest.mock('./foo');
foo.mockReturnValue('mocked foo');
const fooModule = jest.requireActual('./foo')
test('mocked return value from foo()', () => {
expect(bar()).toEqual('mocked foo');
});
test('real return value from foo()', () => {
foo.mockImplementation(fooModule.foo);
expect(bar()).toEqual('real foo');
});

Is it possible to clear the mock of a module in JEST?

I want to mock the function forgotPassword inside the module authenticationPlugin/App, So i am doing this
jest.mock("authenticationPlugin/App", () => ({
forgotPassword (email: string) {
const msg='success'
email='a'
return msg
}
}))
Now i want to clear the mock of authenticationPlugin/App and have a different implementation for the forgotPassword method
So i did this
jest.clearAllMocks();
jest.mock("authenticationPlugin/App", () => ({
forgotPassword (email: string) {
throw new Error(<any>{'err':{'message':'Network Error'}})
}
}))
Now i expect the forgotPassword method to have a different implementation after clearing the mocks in for the module authenticationPlugin/App but it doesn't change...
If you want to have a different implementation for the mock in each test, you can use jest.fn instead.
Expanding on your code, it could look like this:
it('returns success', () => {
authApp.forgotPassword = jest.fn((email: string) => {
const msg='success'
email='a'
return msg
});
// Your test code here.
});
test('returns error', () => {
authApp.forgotPassword = jest.fn((email: string) => {
throw new Error(<any>{'err':{'message':'Network Error'}})
});
// Your test code here.
});

How to mock a node module with jest within a class?

I need to mock the DNS node module in a class but I am unsure how to do so as it is enclosed in the class. Here is a sample of what the class looks like...
import { lookup } from 'dns';
class Foo {
// ...
protected async _bar(IP: string) {
// I want to mock "lookup"
await new Promise<undefined>((resolve, reject) => {
lookup(IP, (err, addr) => {
if (err) reject(new Error('DNS Lookup failed for IP_ADDR ' + IP));
resolve();
});
});
// If dns found then return true
return true;
}
// ...
}
I would like to create a test file foo.spec.ts that contains a test similar to the following:
import { Foo } from './Foo';
describe('Foo', () => {
it('Bar Method returns true on success', () => {
const test = new Foo();
expect(test._bar('192.168.1.1')).resolves.toBeTruthy();
});
});
I am unsure how to mock the lookup call within the class Foo given that the class definition is in a separate file from the test itself.
Any help would be appreciated!
The way you are using lookup won't work since it doesn't return a Promise...
...but you can convert it to a version that does return a Promise by using util.promisify.
The code would end up looking something like this:
import { lookup as originalLookup } from 'dns'; // <= import original lookup...
import { promisify } from 'util';
const lookup = promisify(originalLookup); // <= ...and promisify it
export class Foo {
async _bar(IP: string) {
await lookup(IP).catch(err => { throw new Error('Failed'); });
return true;
}
}
You could then mock lookup in your test using jest.mock like this:
import { Foo } from './Foo';
jest.mock('dns', () => ({
lookup: (hostname, callback) => {
hostname === 'example.com' ? callback() : callback('error');
}
}))
describe('Foo', () => {
it('Bar Method returns true on success', async () => {
const test = new Foo();
await expect(test._bar('example.com')).resolves.toBeTruthy(); // Success!
await expect(test._bar('something else')).rejects.toThrowError('Failed'); // Success!
});
});
Note that the mock needs to be created using jest.mock (and not something like jest.spyOn) since calls to jest.mock get hoisted and run first. The mock needs to be in place before Foo.js is imported since the first thing it does is create and store the promisified lookup.
From jest's tutorial,
jest.mock('./sound-player', () => {
return function() {
return {playSoundFile: () => {}};
};
});
so you could do sth like jest.mock('dns', () => ({ ... }));

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

Jest: Change output of manual mock for different tests within a test suite

Let's say I have the following two files:
// index.js
...
import { IS_IOS } from 'common/constants/platform';
...
export const myFunction = () => (IS_IOS ? 'foo' : 'bar');
// index.test.js
...
import { myFunction } from './index';
jest.mock('common/constants/platform', () => ({ IS_IOS: true }));
describe('My test', () => {
it('tests behavior on IOS', () => {
expect(myFunction()).toBe('foo');
});
// --> Here I want to change the value of IS_IOS to false
it('tests behavior if NOT IOS', () => {
expect(myFunction()).toBe('bar');
});
});
As you see my mocking function returns IS_IOS: true. I want it to return IS_IOS: false after my first test. How would I do that?
I also tried an adaptation of the solution here but I couldn't get it work, because there the mock returns a function:
module.exports = {
foo: jest.genMockFunction();
}
whereas my mock should return a boolean value which is not called inside the file I'm testing.
That's what I did here:
// common/constants/__mock__/platform
export const setIsIos = jest.fn(val => (IS_IOS = val));
export let IS_IOS;
// index.test.js
...
import { IS_IOS, setIsIos } from 'common/constants/platform';
jest.mock('common/constants/platform');
describe('My test', () => {
setIsIos('foo');
it('tests behavior on IOS', () => {
expect(myFunction()).toBe('foo');
});
setIsIos('bar');
it('tests behavior if NOT IOS', () => {
expect(myFunction()).toBe('bar');
});
});
Oddly when console-logging, i.e. console.log(IS_IOS); I get the expected values. The test however seems to use the original value, i.e. undefined.
Add jest.resetModules() to the beforeEach() call of that describe() test suite:
describe('EventManager', () => {
beforeEach(() => {
jest.resetModules();
});
...
Additionally I found A more complete example on how to mock modules with jest here

Categories

Resources