I'm trying to write a test for a utility function in our project, which uses the formatDuration module from date-fns/formatDuration. Can anyone help me creating a mock for this module with Jest and be able to test if formatDuration was called with expected arguments?
This is my current setup:
// ../utilites/date.ts
import formatDuration from "date-fns/formatDuration";
import subSeconds from "date-fns/subSeconds";
import intervalToDuration from "date-fns/intervalToDuration";
export const createDurationFromSeconds = (seconds: number) => {
const end = new Date();
const start = subSeconds(end, seconds);
return intervalToDuration({ start, end });
};
type FunctionArgs = Parameters<typeof formatDuration>;
export const formatDurationHelper = (
duration: FunctionArgs[0],
options?: FunctionArgs[1]
) => {
//...do something with options.format
return formatDuration(duration, options);
};
// ../utilites/date.test.ts
import formatDuration from "date-fns/formatDuration";
import { formatDurationHelper, createDurationFromSeconds } from "./date";
jest.mock("date-fns/formatDuration", () => {
return {
__esModule: true,
default: jest.fn()
};
});
describe("formatDurationWithLocale", () => {
it("created mocked formatDuration", () => {
expect(jest.isMockFunction(formatDuration)).toBe(true); // <- this test is successful
});
it("does something with options.format", () => {
const duration = createDurationFromSeconds(1);
formatDurationHelper(duration, {
format: ["seconds"]
});
expect(formatDuration).toHaveBeenCalled(); // <- this test is unsuccessful
expect(formatDuration).toHaveBeenCalledWith(duration, { // <- this test is unsuccessful
format: ["seconds"]
});
});
});
expect(jest.isMockFunction(formatDuration)).toBe(true) is successful, but the others fail...
Thanks in advance!
Related
I am not sure I how to write unit test case file for guard in nestjs. I have below Role.guard.ts file. I have to create Role.guard.spec.ts file. can somebody help please?
import { Injectable, CanActivate, ExecutionContext, Logger } from '#nestjs/common';
import { Reflector } from '#nestjs/core';
import { ROLES_KEY } from './roles.decorator';
import { Role } from './role.enum';
#Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const requiredRoles = this.reflector.getAllAndOverride<string[]>(ROLES_KEY, [
context.getHandler(),
context.getClass(),
]);
const { user } = context.switchToHttp().getRequest();
if (!user.role) {
Logger.error('User does not have a role set');
return false;
}
if (user.role === Role.Admin) {
return true;
}
if (!Array.isArray(requiredRoles) || !requiredRoles.length) {
// No #Roles() decorator set, deny access as not admin
return false;
}
if (requiredRoles.includes(Role.All)) {
return true;
}
return requiredRoles.includes(user.role);
}
}
I wrote below code but coverage issue is coming.
import { createMock } from '#golevelup/ts-jest';
import { ExecutionContext } from "#nestjs/common";
import { Reflector } from "#nestjs/core";
import { RolesGuard } from "./roles.guard";
describe('RolesGuard', () => {
let guard: RolesGuard;
let reflector: Reflector;
beforeEach(() => {
reflector = new Reflector();
guard = new RolesGuard(reflector);
});
it('should be defined', () => {
expect(guard).toBeDefined();
});
it('should return false if user does not exist', () => {
reflector.getAllAndOverride = jest.fn().mockReturnValue(true);
const context = createMock<ExecutionContext>();
const canActivate = guard.canActivate(context);
expect(canActivate).toBe(false);
})
})
below lines are not getting covered.
if (user.role === Role.Admin) {
return true;
}
if (!Array.isArray(requiredRoles) || !requiredRoles.length) {
// No #Roles() decorator set, deny access as not admin
return false;
}
if (requiredRoles.includes(Role.All)) {
return true;
}
return requiredRoles.includes(user.role);
Edit 1:-
Below test cases are covering my some part of code.
it('should return true if user exist', () => {
reflector.getAllAndOverride = jest.fn().mockReturnValue(true);
const context = createMock<ExecutionContext>({
switchToHttp: () => ({
getRequest: () => ({
user: {
role:'admin'
}
}),
}),
});
const canActivate = guard.canActivate(context);
expect(canActivate).toBe(true);
})
it('should return false if user does not exist', () => {
reflector.getAllAndOverride = jest.fn().mockReturnValue(true);
const context = createMock<ExecutionContext>({
switchToHttp: () => ({
getRequest: () => ({
user: {
role:'user'
}
}),
}),
});
const canActivate = guard.canActivate(context);
expect(canActivate).toBe(false);
})
But below code still not getting covered.
if (requiredRoles.includes(Role.All)) {
return true;
}
return requiredRoles.includes(user.role);
}
Can sombody help me on the same?
Looks like you need to be able to craft the payload appropriately such that your code is hit. There are a variety of ways to do this but, in a "Nest"-y way, we can try something like what the docs tell us. Notice in that link that the provided testing utilities make this a lot easier to mock.
import { createMock } from '#golevelup/ts-jest';
import { ExecutionContext } from "#nestjs/common";
import { Reflector } from "#nestjs/core";
import { RolesGuard } from "./roles.guard";
describe('RolesGuard', () => {
let guard: RolesGuard;
let reflector: Reflector
beforeEach(async () => {
reflector = new Reflector();
guard = new RolesGuard(reflector);
});
it('should be defined', () => {
expect(guard).toBeDefined();
});
it('should return false if user does not exist', () => {
reflector.getAllAndOverride = jest.fn().mockReturnValue(true);
const context = createMock<ExecutionContext>();
const canActivate = guard.canActivate(context);
expect(canActivate).toBe(false);
})
it('should return false if user does exist', () => {
reflector.getAllAndOverride = jest.fn().mockReturnValue(true);
// Mock the "class" type of this so we can get what we want.
// We want to tell it to return an object where role is defined.
const context = createMock<ExecutionContext>({
switchToHttp: () => ({
getRequest: () => ({
user: { role: { /* enter your data here */ }
}),
}),
});
const canActivate = guard.canActivate(context);
expect(canActivate).toBe(false);
})
})
From here your context is successfully hydrated with whatever you want and the second test should start showing up as covering your other code branches. You can now edit the role attribute to look however you want. Notice also in the above beforeEach call you should be able to switch to using testing modules instead. This is not exhaustive though, you'll likely need to add additional test cases to cover your other branches. If you follow as I've done here, that should be relatively trivial.
Does what I've done here make sense? By crafting a custom payload to the object, the roles attribute is present, allowing the test to evaluate other cases. I got my override from the createMock function directly from the docs for golevelup.
I'm having trouble getting the AWS Secrets Manager module mocked for the jest unit tests... The part it errors on is the .promise(). When I remove that, the code doesn't work for the real Secrets Manager so I think it needs to stay there. How do I mock the getSecretData function so that getSecretData.promise() will work for the mock?
Here is the SecretsManager.js code:
import AWS from 'aws-sdk';
export class SecretsManager {
constructor() {
AWS.config.update({
region: 'us-east-1',
});
this.secretsManager = new AWS.SecretsManager();
}
async getSecretData(secretName) {
try {
const response = await this.secretsManager.getSecretValue({
SecretId: secretName,
}).promise();
const secretString = response.SecretString;
const parsedSecret = JSON.parse(secretString);
return parsedSecret;
} catch (e) {
console.log('Failed to get data from AWS Secrets Manager.');
console.log(e);
throw new Error('Unable to retrieve data.');
}
}
}
Here is the SecretsManager.test.js code:
import { SecretsManager } from '../utils/SecretsManager';
jest.mock('aws-sdk', () => {
return {
config: {
update(val) {
},
},
SecretsManager: function () {
return {
async getSecretValue({
SecretId: secretName
}) {
return {
promise: function () {
return {
UserName: 'test',
Password: 'password',
};
}
};
}
};
}
}
});
describe('SecretsManager.js', () => {
describe('Given I have a valid secret name', () => {
describe('When I send a request for test_creds', () => {
it('Then the correct data is returned.', async () => {
const mockReturnValue = {
UserName: 'test',
Password: 'password',
};
const logger = getLogger();
const secretManager = new SecretsManager();
const result = await secretManager.getSecretData('test_creds');
expect(result).toEqual(mockReturnValue)
});
});
describe('When I send a request without data', () => {
it('Then an error is thrown.', async () => {
const secretManager = new SecretsManager();
await expect(secretManager.getSecretData()).rejects.toThrow();
});
});
});
});
This is the error I get when running the tests:
this.secretsManager.getSecretValue(...).promise is not a function
Any suggestions or pointers are greatly appreciated!
Thank you for looking at my post.
I finally got it to work... figures it'd happen shortly after posting the question, but instead of deleting the post I'll share how I changed the mock to make it work incase it helps anyone else.
Note: This is just the updated mock, the tests are the same as in the question above.
// I added this because it's closer to how AWS returns data for real.
const mockSecretData = {
ARN: 'x',
Name: 'test_creds',
VersionId: 'x',
SecretString: '{"UserName":"test","Password":"password"}',
VersionStages: ['x'],
CreatedDate: 'x'
}
jest.mock('aws-sdk', () => {
return {
config: {
update(val) {
},
},
SecretsManager: function () {
return {
getSecretValue: function ( { SecretId } ) {
{
// Adding function above to getSecretValue: is what made the original ".promise() is not a function" error go away.
if (SecretId === 'test_creds') {
return {
promise: function () {
return mockSecretData;
}
};
} else {
throw new Error('mock error');
}
}
}
};
}
}});
I ran into this issue as well. There may be a more elegant way to handle this that also allows for greater control and assertion, but I haven't found one. Note that the in-test option may work better with newer versions of Jest.
I personally solved this issue by making use of manual mocks and a custom mock file for aws-sdk. In your case, it would look something like the following:
# app_root/__tests__/__mocks__/aws-sdk.js
const exampleResponse = {
ARN: 'x',
Name: 'test_creds',
VersionId: 'x',
SecretString: '{"UserName":"test","Password":"password"}',
VersionStages: ['x'],
CreatedDate: 'x'
};
const mockPromise = jest.fn().mockResolvedValue(exampleResponse);
const getSecretValue = jest.fn().mockReturnValue({ promise: mockPromise });
function SecretsManager() { this.getSecretValue = getSecretValue };
const AWS = { SecretsManager };
module.exports = AWS;
Then in your test file:
// ... imports
jest.mock('aws-sdk');
// ... your tests
So, in a nutshell:
Instead of mocking directly in your test file, you're handing mocking control to a mock file, which Jest knows to look for in the __mocks__ directory.
You create a mock constructor for the SecretsManager in the mock file
SecretsManager returns an instance with the mock function getSecretValue
getSecretValue returns a mock promise
the mock promise returns the exampleResponse
Bada boom, bada bing. You can read more here.
I ran into a same issue, I have tried to solve as below. It worked perfectly in my case.
Terminalsecret.ts
import AWS from 'aws-sdk';
AWS.config.update({
region: "us-east-1",
});
const client = new AWS.SecretsManager();
export class Secret {
constructor(){}
async getSecret(secretName: string) {
let secret: any;
const data = await client.getSecretValue({ SecretId: secretName).promise();
if ('SecretString' in data) {
secret = data.SecretString;
} else {
const buff = Buffer.alloc(data.SecretBinary as any, 'base64');
secret = buff.toString('ascii');
}
const secretParse = JSON.parse(secret);
return secretParse[secretName];
}
}
Terminalsecret.test.ts
import { SecretsManager as fakeSecretsManager } from 'aws-sdk';
import { Secret } from './terminalSecret';
jest.mock('aws-sdk');
const setup = () => {
const mockGetSecretValue = jest.fn();
fakeSecretsManager.prototype.getSecretValue = mockGetSecretValue;
return { mockGetSecretValue };
};
describe('success', () => {
it('should call getSecretValue with the argument', async () => {
const { mockGetSecretValue } = setup();
mockGetSecretValue.mockReturnValueOnce({
promise: async () => ({ SecretString: '{"userName": "go-me"}' })
});
const fakeName = 'userName';
const terminalSecretMock: TerminalSecret = new TerminalSecret()
terminalSecretMock.getTerminalSecret(fakeName);
expect(mockGetSecretValue).toHaveBeenCalledTimes(1);
});
});
In my calendar.spec.js, I have:
const { google } = require('googleapis')
const googleCalendar = google.calendar('v3')
...
before(() => {
sinon.stub(googleCalendar.calendarList, 'list').resolves({ data: true })
})
after(() => {
googleCalendar.calendarList.list.restore()
})
In my calendar.js, I have:
const { google } = require('googleapis')
const googleCalendar = google.calendar('v3')
let { data } = await googleCalendar.calendarList.list({
auth: oauth2Client
})
But it doesn't appear to be stubbed. It goes ahead and tries to connect to Google Calendar. What am I doing wrong?
You can mock the entire googleapis module with mock-require.
const mock = require('mock-require');
mock('googleapis', {
google: {
calendar: () => ({
calendarList: {
list: () => {
return Promise.resolve({
data: {
foo: 'bar'
}
});
}
}
})
}
});
Once you mocked it, your module will consume the mocked module instead of the original so you can test it. So if you module is exposing a method that calls the API, something like that:
exports.init = async () => {
const { google } = require('googleapis');
const googleCalendar = google.calendar('v3');
let { data } = await googleCalendar.calendarList.list({
auth: 'auth'
});
return data;
}
The test will be
describe('test', () => {
it('should call the api and console the output', async () => {
const result = await init();
assert.isTrue(result.foo === 'bar');
});
});
Here is a small repo to play with it: https://github.com/moshfeu/mock-google-apis
this is me again with very simple example.
In resumen i need a different response for the mocked class.
This is my simple class that return an object
class Producer {
hello() {
return {
message:'Class: Hello',
}
}
}
export default Producer;
this is the mock class in the __mocks__ folder
class Producer {
hello() {
return {
message:'__mocks__: hello',
}
}
}
export default Producer;
This is my test file that works as i expected
import Consumer from './Consumer';
jest.mock('./Producer');
test('simple test 1', () => {
let consumer = new Consumer();
consumer.call();
expect(consumer.response.message).toEqual('__mocks__: hello')
console.log(consumer.response)
// prints on console { message: '__mocks__: hello' }
});
My question is, for other test i need a different response from the mocked file Producer
jest.mock('./Producer').updateOnTheFly( hello() {
return {
message:'UPDATE ON FLY: hello',
}
})
test('simple test 1', () => {
let consumer = new Consumer();
consumer.call();
expect(consumer.response.message).toEqual('UPDATE ON FLY: hello')
});
For something like this it can be easier to not create a manual mock at __mocks__/Producer.js.
Instead, use jest.mock('./Producer'); to auto-mock the module...
...then mock the return value for Producer.prototype.hello as needed:
import Consumer from './Consumer';
import Producer from './Producer';
jest.mock('./Producer'); // <= auto-mock Producer
test('simple test 1', () => {
Producer.prototype.hello.mockReturnValue({ message: 'mocked: hello' });
let consumer = new Consumer();
consumer.call();
expect(consumer.response.message).toEqual('mocked: hello') // Success!
});
test('simple test 2', () => {
Producer.prototype.hello.mockReturnValue({ message: 'UPDATED: hello' });
let consumer = new Consumer();
consumer.call();
expect(consumer.response.message).toEqual('UPDATED: hello') // Success!
});
Well, after some re-search i resolved with this.
class Producer {
hello() {
return {
message:' Class: Hello ',
}
}
}
export default Producer;
Mocking the Producer, it is not neccesary create mock file
import Consumer from './Consumer';
jest.mock('./Producer');
test('simple test 1', () => {
// mocking Producer.hello()
Producer.mockImplementation(() => {
return {
hello: () => {
return {
message: "ths is mocked "
}
},
};
});
let consumer = new Consumer();
consumer.call(); /// Consume.call() uses inside Producer.hello() mocked
})
How can I stub the redis publish method?
// module ipc
const redis = require('redis');
module.exports = class IPC {
constructor() {
this.pub = redis.createClient();
}
publish(data) {
this.pub.publish('hello', JSON.stringify(data));
}
}
and another module
// module service
module.exports = class Service {
constructor(ipc) {
this.ipc = ipc;
}
sendData() {
this.ipc.publish({ data: 'hello' })
}
}
How could I stub the private variable pub in IPC class?
I could stub the redis.createClient by using proxyquire, if I do that it will complain publish undefined
My current test code
let ipcStub;
before(() => {
ipcStub = proxyquire('../ipc', {
redis: {
createClient: sinon.stub(redis, 'createClient'),
}
})
});
it('should return true', () => {
const ipc = new ipcStub();
const ipcPublishSpy = sinon.spy(ipc, 'publish')
const service = new Service(ipc);
service.sendData();
assert.strictEqual(true, ipcPublishSpy.calledOnce);
})
You just need to set the spy on the publish method, no need for the proxyquire.
e.g.
import {expect} from 'chai';
import sinon from 'sinon';
class IPC {
constructor() {
this.pub = {
publish:() => {} //here your redis requirement
};
}
publish(data) {
this.pub.publish('hello', JSON.stringify(data));
}
}
class Service {
constructor(ipc) {
this.ipc = ipc;
}
sendData() {
this.ipc.publish({ data: 'hello' })
}
}
describe('Test Service', () => {
it('should call publish ', () => {
const ipc = new IPC;
sinon.spy(ipc.pub,'publish');
const service = new Service(ipc);
service.sendData();
expect(ipc.pub.publish.calledOnce).to.be.true;
});
});
I found a way to do it just by using sinon
Just need to create a stub instance using sinon.createStubInstance,
then this stub will have all the functionalities from sinon without the implementation of the object (only the class method name)
let ipcStub;
before(() => {
ipcStub = sinon.createStubInstance(IPC)
});
it('should return true', () => {
const ipc = new ipcStub();
const service = new Service(ipc);
service.sendData();
assert.strictEqual(true, ipc.publishSystem.calledOnce);
})