jest check module function calls count - javascript

I am pretty new to javascript and jest too and I have this use case in my tests
jest.mock('./db' , ()=>{
saveProduct: (product)=>{
//someLogic
return
},
updateProduct: (product)=>{
//someLogic
return
}
})
This works fine and I can run my tests against it, but I still want to assert in my test cases that updateProduct is called 3 times, how can I achieve this?

Here is an example how can you test these mock fn
const saveProductMock = jest.fn((product)=>{
//someLogic
return
});
const updateProductMock = jest.fn((product)=>{
//someLogic
return
})
jest.mock('./db' , ()=>{
saveProduct: saveProductMock,
updateProduct: updateProductMock
})
expect(saveProductMock).toHaveBeenCalled()
expect(saveProductMock.mock.calls[0][0]).toBe(product)

You run mock on an exported module, then everywhere you import the module, it becomes mocked.
import db from './db'; // import db
jest.mock('./db', () => ({ // I think you want to return an object
saveProduct: (product) => {
//someLogic
return
},
updateProduct: (product) => {
//someLogic
return
}
}));
// assert
expect(db.updatePorduct).toHaveBeenCalledTimes(3);

Related

How to only mock

jest unit test question here. If I am testing function A, and function A uses function B,
import { funcB } = require('./FileB');
const funcA = () => {
const someData = funcB();
}
Does this mean that, in my unit test file testing funcA, in order to mock funcB, do i HAVE to import the function to mock it? which means the file wouldn't use that function but simply there for mocking purposes or do I have to mock the entire module in which funcB lives on and mock the implementation of that single function?
my goal here is, I dont want every single test in my unit test file to have funcB mocked, I only want it mocked on some test but not all while testing funcA.
So if i have 4 test and 3 of them need it mocked but 2 need its actuall implementation, whats a best way to approach this?
One possible solution - use spyOn and mockRestore for following implementation:
export const funcB = () => {
console.log('original implementation of B')
return 42;
}
import { funcB } from './fileb';
export const funcA = () => {
const someData = funcB();
return someData;
}
import * as fileB from "../funcab/fileb";
import { funcA } from "../funcab/filea";
describe('testing 1...2...3...', () => {
const spy = jest.spyOn(fileB, 'funcB');
afterEach(() => {
spy.mockRestore();
})
it('will test with mocked funcB', () => {
spy.mockReturnValue(1);
expect(funcA()).toEqual(1);
});
it('will test without mocked funcB', () => {
expect(funcA()).toEqual(42);
})
})
Here is a post about Jest mock and spy — mockClear vs mockReset vs mockRestore. I hope you'll find it usefull.
Answer for comment - do you mean sth like this?
const mockFuncB = jest.fn();
jest.mock("../funcab/fileb", () => ({
funcB: () => mockFuncB(),
}));
then the test would look like:
it('will test with mocked funcB', () => {
mockFuncB.mockReturnValue(1);
expect(funcA()).toEqual(1);
});

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

Mock exported function in module

I have two modules which contain exported functions. "ModuleB" uses a function from "ModuleA". Now I want to test "ModuleB" and mock the used function from "ModuleA".
I use ES6 with babel. For testing I use karma and jasmine.
I tried using babel-rewire and inject-loader, but it just does not work. I'm kind of new to all this and I guess I'm just doing something wrong. So any help is appreciated!
moduleA.js
export function getResult() {
return realResult;
}
moduleB.js
import * as ModuleA from './moduleA'
export function functionToTest() {
let result = ModuleA.getResult();
// do stuff with result which I want to test
}
my test
it("does the right thing", () => {
// I tried using rewire, but this gives me an exception:
ModuleB.__Rewire__('ModuleA', {
getResult: () => {
return fakeResult;
}
});
let xyz = ModuleB.functionToTest(canvas, element);
});
Take a look to this library https://www.npmjs.com/package/mockery I thought It's exactly what do you want. Hope it helps!
EDITED:
Basically you have to use mockery in each test to mock your function, the only problem is that you must use require against import with the modules you want to mock. Look this example:
const moduleUnderTest = './moduleB.js';
const moduleA_fake = { getResult: () => { return "This is a fake result"; } } ;
describe("Mocking functions", () => {
it('Should be Fake Result', (done) => {
mock.registerAllowable(moduleUnderTest);
mock.registerMock('./moduleA.js', moduleA_fake);
mock.enable({ useCleanCache: true });
let ModuleB = require(moduleUnderTest);
let res = ModuleB.functionToTest();
res.should.be.eql("This is a fake result");
mock.disable();
mock.deregisterAll();
done();
});
it('Should be Real Result', (done) => {
let ModuleB = require(moduleUnderTest);
let res = ModuleB.functionToTest();
res.should.be.eql("real foo");
done();
});
});
You could see the complete example Here
For anyone who is interested in how it works with babel-rewire: The trick is to use __RewireAPI__.
import * as ModuleB from '../ModuleB'
import {__RewireAPI__ as ModuleBRewire} from '../ModuleA';
it("does the right thing", () => {
let moduleAMock = {
getResult: () => {
return fakeResult;
}
}
ModuleBRewire.__Rewire__('ModuleA', moduleAMock);
let xyz = ModuleB.functionToTest();
});

Categories

Resources