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

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', () => ({ ... }));

Related

How can I mock a class using jest?

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.

jest check module function calls count

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

How to mock a plugin in Jest

My unit test is not setup properly, meaning that I am not correctly mocking my imported plugin function.
How do I correctly mock my logData function? In the plugin the function returns undefined and that is by choice. I just need to make sure I console.log whatever is passed to it.
The current error I get is "Cannot spy the logData property because it is not a function; undefined given instead"
logData.js - the plugin (just a wrapper around console.log statements)
export function logData (dataToLog) {
const isLoggingData = localStorage.getItem('isLoggingData')
if (isLoggingData) {
console.log(dataToLog)
}
}
export default {
install: function (Vue) {
Vue.prototype.$logData = logData
}
}
logData.spec.js - I mocked localStorage but I need to mock the plugin logData
import Vue from 'vue'
import { createLocalVue } from '#vue/test-utils'
import logData from './logData'
class LocalStorageMock {
constructor () {
this.store = {}
}
getItem (key) {
return this.store[key] || null
}
setItem (key, value) {
this.store[key] = value.toString()
}
removeItem (key) {
delete this.store[key]
}
}
global.localStorage = new LocalStorageMock()
const localVue = createLocalVue()
const dataToLog = 'data to be logged'
localVue.use(logData)
const mockLogDataFunction = jest.spyOn(localVue.prototype, 'logData')
describe('logData plugin', () => {
// PASSES
it('adds a $logData method to the Vue prototype', () => {
console.log(wrapper.vm.$logData)
expect(Vue.prototype.$logData).toBeUndefined()
expect(typeof localVue.prototype.$logData).toBe('function')
})
// Now passes
it('[positive] console.logs data passed to it', () => {
global.localStorage.setItem('isLoggingData', true)
const spy = jest.spyOn(global.console, 'log')
localVue.prototype.$logData('data to be logged')
expect(spy).toHaveBeenCalledWith(dataToLog)
// expect(mockLogDataFunction).toHaveBeenCalled()
// console.log(localVue)
// expect(localVue.prototype.$logData(dataToLog)).toMatch('data to be logged')
})
// PASSES
it('[negative] does not console.log data passed to it', () => {
const spy = jest.spyOn(global.console, 'log')
global.localStorage.removeItem('isLoggingData')
localVue.prototype.$logData(dataToLog)
expect(spy).not.toHaveBeenCalled()
// const spy = jest.spyOn(this.$logData, 'console')
// expect(localVue.prototype.$logData(dataToLog)).toBe(und efined)
// expect(spy).toBe(undefined)
})
})
You are doing things wrong.
jest.spyOn(object, methodName). You need to pass localVue.prototype and 'logData', as arguments, to track if this method was called.
Creates a mock function similar to jest.fn but also tracks calls to
object[methodName]. Returns a Jest mock function.
To check if method was called – expect(spy).toHaveBeenCalled().
So change some lines of your code:
const mockLogDataFunction = jest.spyOn(localVue.prototype, '$logData')
// invoke it and then ensure that that method is really called
localVue.prototype.$logData('foo')
expect(mockLogDataFunction).toHaveBeenCalled()

Several mocks of one module in one file with jest

I have to functions for example a, b. Both of them are elements of one module, and they are re-exported in index.js. Function a invokes function b.
It all works if i use jest.mock on the top of the file, but if i want to specify different mock implementation of b function in every it block it doesn't work. Also i try'ed to use jest.doMock but it doesn't work as well.
a.js
import * as fromDependencies from '.'
export function(arg) {
return !fromDependencies && !arg;
}
b.js
export function b() {
//some code
return boolean;
}
index.js
export * from 'a.js',
export * from 'b.js'
testFile
import a from '../a.js';
describe('isGroupOverlaidTest', () => {
it('should return false', () => {
jest.mock('../.', () => ({
b: jest.fn(() => true);
}))
expect(a(true)).toBe(false);
});
it('should return true', function() {
jest.mock('../.', () => ({
b: jest.fn(() => false);
}))
expect(a(false)).toBe(false);
});
});
The results are fake, anyway i want to call my mocked function not the original one. When i have jest.mock in the top of the file it works but i can achieve just one mock for on file. Do mock does't work. I'll be really grateful if somebody can provide some example how i can resolve that ;).
You could use a spy instead of a mock. Since a exports a function instead of an object then you'd have to wrap it.
import a from '../a.js'
const aWrapped = { a }
describe('isGroupOverlaidTest', () => {
it('should return false', () => {
const mockA = jest.fn()
const spyedA jest.spyOn(aWrapped, a).mockReturnValue(mockA)
expect(spyedA(true)).toBe(false);
});
});
Something like that.

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