In our node CLI we have a simple method:
'use strict';
const ora = require('ora');
module.exports = function startSpinner({ textOnStart, color, spinnerType }) {
const spinner = ora({
text: textOnStart,
color: color || 'cyan',
spinner: spinnerType || ''
}).start();
};
We try to use jest to test this method. We have two tests to achieve:
Testing that ora has been called with proper object argument
Testing that the method start() was called afterward
That being said we cannot achieve to mock ora module properly.
ora is a third party that is basically constructed as follow:
class Ora {
constructor(options){}
start(){ }
}
const oraFactory = function (opts) {
return new Ora(opts);
};
module.exports = oraFactory;
module.exports.default = oraFactory;
We are looking for a way to mock ora.
We tried to use auto mock:
const ora = require('ora');
jest.mock('ora');
const startSpinner = require('./startSpinner');
describe('startSpinner', () => {
beforeEach(() => {
startSpinner({});
});
describe('ora', () => {
it('should call ora', () => {
expect(ora).toHaveBeenCalled();
});
it('should call ora start', () => {
expect(ora.start).toHaveBeenCalled();
});
});
});
But both tests fail with respectively:
Matcher error: received value must be a mock or spy function
Received has type: function
Received has value: [Function oraFactory]
and
Matcher error: received value must be a mock or spy function
Received has value: undefined
We tried to use a custom mock:
const ora = require('ora');
jest.mock('ora', () => {
return jest.fn().mockImplementation(() => {
return { start: jest.fn() };
});
});
and it ends up with the same exact result.
We even tried to convert our test to typescript and then use:
import * as ora from 'ora';
const startMock = jest.fn();
jest.mock('ora', () => {
return jest.fn().mockImplementation(() => {
return { start: startMock };
});
});
Then we were able to test successfuly that ora was called. But we ended up with an error for expect(ora.start).toHaveBeenCalled(); or even expect((ora as any).start).toHaveBeenCalled();:
error TS2339: Property 'start' does not exist on type 'typeof
import("/Users/Dev/cli/node_modules/ora/index")'.
Surely caused by the fact the type definition of imported ora is export default function ora(options?: Options | string): Ora;
How to then mock a third party like ora in jest's node test environnement?
You've got a couple of options:
You can mock ora like this:
jest.mock('ora', () => {
const start = jest.fn();
const result = { start };
return jest.fn(() => result);
});
...and then call ora to get the object it returns (since it always returns the same object) and use that object to access start:
it('should call ora start', () => {
const result = ora();
expect(result.start).toHaveBeenCalled(); // Success!
});
Or if you want you can attach the start mock as a property to the ora mock as an easy way to access it during your tests like this:
const ora = require('ora');
jest.mock('ora', () => {
const start = jest.fn();
const result = { start };
const ora = jest.fn(() => result);
ora.start = start; // attach the start mock to ora
return ora;
});
const startSpinner = require('./startSpinner');
describe('startSpinner', () => {
beforeEach(() => {
startSpinner({});
});
describe('ora', () => {
it('should call ora', () => {
expect(ora).toHaveBeenCalled(); // Success!
});
it('should call ora start', () => {
expect(ora.start).toHaveBeenCalled(); // Success!
});
});
});
Related
I have a function that calls a jQuery function.
the jQuery function called dataFunc and should return an object.
I want to test the promise, not the dataFunc function.
For that, I want to mock the response that dataFunc should return
I want this row const { data } = await service.auth( buttonData ); data to return
{ access_level: 0 };
How can I do that?
This is my code:
This function with the promise I want to test:
auth(buttonData){
const myPromise = new Promise((resolve, reject) => {
const success = (e, data) => {
resolve({data, error: null});
};
const error = () => {
resolve({data: null, error: 'Error'});
};
jQuery(buttonData).dataFunc({
success,
error,
});
});
return myPromise;
}
This is what I have done in jest so far:
describe('service.test.js', () => {
beforeEach(() => {
global.jQuery = () => {
return {
dataFunc: jest.fn(() => ({access_level: 0})),
};
};
});
afterEach(() => {
jest.clearAllMocks();
});
test('should do something', async () => {
// Arrange
const service = new Service();
const approveButton = document.createElement('button');
// Act
const {data} = await service.auth(buttonData);
console.log(data);
});
});
To fulfill auth function you need either reject or resolve the value.
However, when you mock jQuery method dataFunc to return an explicit value, you override the default behavior and it never calls resolve or reject. Therefore your promise will hang.
You don't necessarily need to mock but provide the original functionality dataFunc carries or provide one that is necessary for the current test.
To fix your example you can pass the argument and call it.
global.jQuery = () => {
return {
dataFunc: ({success, error}) => {
success(jest.fn(), {access_level: 0})
},
};
};
im using an http request function as the handler function in middy and then use the ssm middleware to fetch some ssm parameters before initiating the http request.
like this:
const makeThirdPartyServiceRequest = middy(async ({ params }) => {
logger.info(`SENDING Request to ${endpoint} API`)
const url = `https://someurltoathirdpartyservice`
const options = {
method: 'POST',
body: params
}
return helpers.makeRequest(url, options)
})
makeThirdPartyServiceRequest.use(ssm(......))
However in my jest unit test Im trying to mock makeThirdPartyServiceRequest and explicitly say it should resolve to a value:
jest.mock('../src/thirdPartyService', () => ({
__esModule: true,
default: {
...(jest.requireActual('../src/thirdPartyService') as { default: {} }).default,
makeThirdPartyServiceRequest: jest.fn()
}
}))
export {}
import thirdPartyService from '../src/thirdPartyService'
And then in the test i say:
describe('makeThirdPartyServiceRequest()', () => {
it('should makeThirdPartyServiceRequest', async () => {
// Given
// })
const mockedThirdPartyServiceRequest = mocked(thirdPartyService.makeThirdPartyServiceRequest).mockResolvedValue({})
// When
const result = await thirdPartyService.makeThirdPartyServiceRequest(something)
// Then
expect(mockedThirdPartyServiceRequest).toHaveBeenCalledTimes(1)
expect(mockedThirdPartyServiceRequest.mock.calls[0][0].params.toString()).toBe(expectedParams)
})
})
However for some reason the middy middleware is still being invoked, which i clearly dont want and i have tried to mock away... what am i doing wrong?
You need to mock middy instead, to make it becomes a useless function. That function recipe a function as a parameter and return that parameter.
import thirdPartyService from '../src/thirdPartyService'
jest.mock('#middy/core', () => {
return (handler) => {
return {
use: jest.fn().mockReturnValue(handler), // ...use(ssm()) will return handler function
}
}
})
describe('thirdPartyService()', () => {
beforeEach(() => {
jest.spyOn(helpers, 'makeRequest') // spy on helpers unit
})
describe('makeThirdPartyServiceRequest', () => {
it('should make a request with correct parameters', async () => {
// Given
const url = `https://someurltoathirdpartyservice`
const params = 'any params'
const apiResponse = 'any response'
mocked(helpers.makeRequest).mockResolvedValue(apiResponse)
// When
const actual = await thirdPartyService.makeThirdPartyServiceRequest(params)
// Then
expect(actual).toBe(apiResponse)
expect(helpers.makeRequest).toHaveBeenCalledWith(
url,
{
method: 'POST',
body: params
}
)
})
})
})
hoangdv answer is also valid, but i will answer as well how i continued.
if you completely want to mock middy you mock like following:
jest.mock('#middy/core', () => {
return (handler) => {
return {
use: jest.fn().mockImplementation(() => {
// ...use(ssm()) will return handler function
return {
before: jest.fn().mockReturnValue(handler)
}
})
}
}
})
However if you dont want to completely mock middy, you can instead mock the async getInternal function from middy/util called in before like this:
jest.doMock('#middy/util', () => ({
...(jest.requireActual('#middy/util') as {}),
getInternal: jest.fn()
}))
import { getInternal } from '#middy/util'
and then in the test
describe('thirdPartyService()', () => {
beforeEach(() => {
jest.spyOn(helpers, 'makeRequest') // spy on helpers unit
})
describe('makeThirdPartyServiceRequest', () => {
it('should make a request with correct parameters', async () => {
// Given
const url = `https://someurltoathirdpartyservice`
const params = 'any params'
const apiResponse = 'any response'
mocked(getInternal).mockResolvedValue({
twilioSecrets: { accountSid: 'someSID', serviceId:
'someServiceID', token: 'someToken' }
})
mocked(helpers.makeRequest).mockResolvedValue(apiResponse)
// When
const actual = await thirdPartyService.makeThirdPartyServiceRequest(params)
// Then
expect(actual).toBe(apiResponse)
expect(helpers.makeRequest).toHaveBeenCalledWith(
url,
{
method: 'POST',
body: params
}
)
})
})
})
this will mock the async part of middy.
I have a module that looks like this:
const config = require('config')
const isActive = config.get('isActive')
const infoMap = new Map()
const set = (key, value) => {
infoMap.set(key, value)
}
const get = (key) => infoMap.get(key)
module.exports={set, get}
and a test where I test the stuff:
let get
let set
beforeEach(() => {
jest.mock('config')
mockIsActive = require('config').get.mockReturnValueOnce(true)
get = require('../cache/mymap').get
set = require('../cache/mymap').set
})
describe('The map', () => {
describe('when data is added', () => {
set('testKey', "testData")
it('should contains the data', async () => {
const dataFromMap = get('testKey')
assert("testData", dataFromMap)
})
})
})
It fails when the set is called with:
set is not a function
Strange is that get works without problems.
You must call the set function inside the it function, otherwise it is not yet defined:
describe('when data is added', () => {
it('should contains the data', async () => {
set('testKey', "testData")
const dataFromMap = get('testKey')
assert("testData", dataFromMap)
})
})
beforeEach runs before each it function, not before describe. This is also why get does work in your example - it is inside the it function.
I'm trying to create a mock for a function createClient where it should return a specific object.
However, for some reason the mock is ignored and it runs the function instead of receiving the mock value.
authorization.js
// some requires here
const createClient = req => {
if (!(req.user && req.user.access_token)) {
throw new Error('Not authorized');
}
...
return { ... }
}
const getUser = async client => { ... }
module.exports = options => {
...
createClient(req) is called here
...
}
authorization.test.js
import authorization from '../../server/middlewares/authorization';
describe('authorization.js', () => {
it('Should do something', async done => {
authorization.createClient = jest.fn(() => ({
client: 'test',
}));
// ACT
const authorizationMiddleware = authorization();
const result = await authorizationMiddleware(someOptions);
// ASSERT
expect(result).toBe('authorized');
done();
});
The error
It seems that the mock for createClient is not working as I wanted to be. It should return the object { client: 'test' }
Your code is incomplete, so I try to give a demo for your case. If you want to mock a private variable in the module scope, the createClient function for your case. You can use rewire package to do that.
E.g.
authorization.js
let createClient = (req) => {
if (!(req.user && req.user.access_token)) {
throw new Error('Not authorized');
}
function getUser() {
return 'real user';
}
return { getUser };
};
const getUser = async (client) => {
return client.getUser();
};
module.exports = (options) => {
const client = createClient(options.req);
return () => getUser(client);
};
authorization.test.js:
const rewire = require('rewire');
describe('61076881', () => {
it('should get user', async () => {
const authorization = rewire('./authorization');
const mClient = { getUser: jest.fn().mockReturnValueOnce('fake user') };
const mCreateClient = jest.fn(() => mClient);
authorization.__set__('createClient', mCreateClient);
const options = { req: { user: { access_token: '123' } } };
const authorizationMiddleware = authorization(options);
const user = await authorizationMiddleware();
expect(user).toEqual('fake user');
expect(mCreateClient).toBeCalledWith(options.req);
expect(mClient.getUser).toBeCalledTimes(1);
});
});
unit test results:
PASS stackoverflow/61076881/authorization.test.js (7.601s)
61076881
✓ should get user (10ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 8.54s, estimated 9s
source code: https://github.com/mrdulin/react-apollo-graphql-starter-kit/tree/master/stackoverflow/61076881
zoomOut(callback) {
// Zooms out the current screen
this.view.current.zoomOut(300).done(() => {
(hasCallback(callback)) && callback();
});
}
I'm trying to test the function above but I keep getting the following error:
TypeError: this.view.current.zoomOut(...).done is not a function
How can I mock this method chain in Jest?
Thanks to BudgieInWA, I was able to solve this problem by returning done.
For those who are testing a React component with Enzyme, here's how you can do it:
it('should call callback', () => {
const wrapper = shallow(<Zoom {...minProps}/>);
const instance = wrapper.instance();
const callback = jest.fn();
instance.view = {
current: {
zoomOut: jest.fn(() => {
return {
done: jest.fn((callback) => {
callback();
})
};
})
}
};
expect(callback).toHaveBeenCalledTimes(0);
instance.zoomOut(callback);
expect(callback).toHaveBeenCalledTimes(1);
});
You could try this:
const mockZoomOut = jest.fn(() => ({ done(cb) { cb(); } }));
const mockThis = {
view: {
current: {
zoomOut: mockZoomOut,
},
},
};
test('it does', () => {
const cb = jest.fn();
zoomOut.apply(mockThis, [cb]);
expect(mockZoomOut).toHaveBeenCalledTimes(1);
expect(cb).toHaveBeenCalledTimes(1);
});
See Jest Mock Functions and fn.apply.
If you are testing the behaviour of the class as a whole, then you could set up the instance that you are testing to have this.view.current.zoomOut be mockZoomOut somehow.