Make partial mock of class with Jest in NodeJS - node.js

I'm new to NodeJS and came from PHP, where creating partial mock was easy. But I'm not able to accomplish the same with Jest in NodeJS.
I have a function extractPayloadDates which accept an instance of dialogflow Agent and taking and parsing data from it. I want to mock only single method getParameter of Agent, because no more methods are used in the tested function. I found this code online but it doesn't work
import { Agent } from '../../src/dialogflow/Agent';
import { extractPayloadDates } from '../../src/intents/extractPayloadDates';
describe('extractPayloadDates', () => {
it('tests extracting string', () => {
const AgentMock = jest.fn<Agent, []>(() => ({
getParameter: () => {
return 'some date';
}
}));
const agent = new AgentMock();
expect(extractPayloadDates(agent)).toEqual('some date');
});
});
This code produce following error:
Type '{ getParameter: () => string; }' is missing the following properties from type 'Agent': payload, webhookClient, chatter, getOriginalRequest, and 13 more.ts(2740)
index.d.ts(124, 53): The expected type comes from the return type of this signature.
I also tried to use jest.spyOn, but the problem is, that I cannot create Agent instance as it needs many other objects.
Edit 3.9.2019 with more code
Agent.ts
export class Agent {
private payload: DialogFlowPayload[] = [];
constructor(readonly webhookClient: WebhookClient, private readonly chatter: Chatter) {}
...
}
WebhookClient and Chatter have more dependencies in their constructor as well...
extractPayloads.spec.ts
import { Agent } from '../../src/dialogflow/Agent';
import { extractPayloadDates } from '../../src/intents/extractPayloadDates';
describe('extractPayloadDates', () => {
it('tests extracting string', () => {
const webhookMock = jest.fn();
const chatter = jest.fn();
const agent = new Agent(webhookMock, chatter);
expect(extractPayloadDates(agent)).toEqual('some date');
});
});
This produce another error:
Argument of type 'Mock' is not assignable to parameter of type 'Chatter'.
Property 'getMessage' is missing in type 'Mock' but required in type 'Chatter'
Do I really have to create also WebhookClient and all its dependencies and do the same with Chatter? If I do, I have to create instances of multiple classes just to mock 1 method in Agent, which then will not use any of dependency.

From the docs of jest
If you want to overwrite the original function, you can use jest.spyOn(object, methodName).mockImplementation(() => customImplementation) or object[methodName] = jest.fn(() => customImplementation);
jest.spyOn call jest.fn internally. So you can only mock getParameter method of agent like this:
extractPayloadDates.ts:
function extractPayloadDates(agent) {
return agent.getParameter();
}
export { extractPayloadDates };
Agent.ts:
interface DialogFlowPayload {}
interface WebhookClient {}
interface Chatter {
getMessage(): any;
}
class Agent {
private payload: DialogFlowPayload[] = [];
constructor(readonly webhookClient: WebhookClient, private readonly chatter: Chatter) {}
public getParameter() {
return 'real data';
}
public otherMethod() {
return 'other real data';
}
}
export { Agent, Chatter };
Unit test, only mock getParameter method of agent, keep the original implementation of otherMethod
import { extractPayloadDates } from './extractPayloadDates';
import { Agent, Chatter } from './Agent';
const webhookMock = jest.fn();
const chatter: jest.Mocked<Chatter> = {
getMessage: jest.fn()
};
const agent = new Agent(webhookMock, chatter);
describe('extractPayloadDates', () => {
it('should only mock getParameter method of agent', () => {
agent.getParameter = jest.fn().mockReturnValueOnce('mocked data');
const actualValue = extractPayloadDates(agent);
expect(actualValue).toEqual('mocked data');
expect(agent.otherMethod()).toBe('other real data');
});
});
PASS src/stackoverflow/57428542/extractPayloadDates.spec.ts
extractPayloadDates
✓ should only mock getParameter method of agent (7ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 2.484s, estimated 3s

Related

can't get the Jest provided ESM example to run

I'm just trying to get the ES6 Class Mocks example provided by Jest to run green.
here's my code repo
it's taken me way to long to even get to this point, but the tests still fail with
TypeError: SoundPlayer.mockClear is not a function
system under test
import SoundPlayer from './sound-player';
export default class SoundPlayerConsumer {
constructor() {
this.soundPlayer = new SoundPlayer();
}
playSomethingCool() {
const coolSoundFileName = 'song.mp3';
this.soundPlayer.playSoundFile(coolSoundFileName);
}
}
the test
import {jest} from '#jest/globals';
import SoundPlayer from './sound-player';
import SoundPlayerConsumer from './sound-player-consumer';
const mockPlaySoundFile = jest.fn();
jest.mock('./sound-player', () => {
return jest.fn().mockImplementation(() => {
return {playSoundFile: mockPlaySoundFile};
});
});
beforeEach(() => {
SoundPlayer.mockClear();
mockPlaySoundFile.mockClear();
});
it('The consumer should be able to call new() on SoundPlayer', () => {
const soundPlayerConsumer = new SoundPlayerConsumer();
// Ensure constructor created the object:
expect(soundPlayerConsumer).toBeTruthy();
});
it('We can check if the consumer called the class constructor', () => {
const soundPlayerConsumer = new SoundPlayerConsumer();
expect(SoundPlayer).toHaveBeenCalledTimes(1);
});
it('We can check if the consumer called a method on the class instance', () => {
const soundPlayerConsumer = new SoundPlayerConsumer();
const coolSoundFileName = 'song.mp3';
soundPlayerConsumer.playSomethingCool();
expect(mockPlaySoundFile.mock.calls[0][0]).toEqual(coolSoundFileName);
});
system under test dependency
export default class SoundPlayer {
constructor() {
this.foo = 'bar';
}
playSoundFile(fileName) {
console.log('Playing sound file ' + fileName);
}
}

How to cover arrow functions in unit testing in angular?

this.service = () => { -- statements -- }
The above statement is to be tested using jasmine unit testing in angular.
Can i get some suggestions for that ?
it("should service call",()=>{ // i want to call the arrow function here like component.service.? what to use in place of '?'. })
It is a function so you call it immediately:
example:
interface Service {
fun: () => string;
}
class Component {
constructor() {
this.service = () => {
return {
fun: () => 'hello'
};
};
}
service: () => Service;
}
Calling it:
const component = new Component();
const service = component.service();
const message = service.fun();
// or in one line:
const message = new Component().service().fun();
Typescript playground example

How to test function in class using jest

I wasn't able to make unit testing worked using jest
I'm trying to test a specific function that's calling or expecting result from other function but I'm not sure why it is not working. I'm pretty new to unit testing and really have no idea how could I make it work. currently this is what I've tried
export class OrganizationService {
constructor() {
this.OrganizationRepo = new OrganizationRepository()
}
async getOrganizations(req) {
if (req.permission !== 'internal' && req.isAuthInternal === false) {
throw new Error('Unauthenticated')
}
const opt = { deleted: true }
return this.OrganizationRepo.listAll(opt)
}
}
This is my OrganizationRepository that extends the MongoDbRepo
import { MongoDbRepo } from './mongodb_repository'
export class OrganizationRepository extends MongoDbRepo {
constructor(collection = 'organizations') {
super(collection)
}
}
and this is the MongoDbRepo
const mongoClient = require('../config/mongo_db_connection')
const mongoDb = require('mongodb')
export class MongoDbRepo {
constructor(collectionName) {
this.collection = mongoClient.get().collection(collectionName)
}
listAll(opt) {
return new Promise((resolve, reject) => {
this.collection.find(opt).toArray((err, data) => {
if (err) {
reject(err)
}
resolve(data)
})
})
}
}
and this is the test that I've made
import { OrganizationService } from '../../../src/services/organization_service'
describe('getOrganizations', () => {
it('should return the list of organizations', () => {
// const OrgRepo = new OrganizationRepository()
const orgService = new OrganizationService()
const OrgRepo = jest.fn().mockReturnValue("[{_id: '123', name: 'testname'}, {_id: '456, name: 'testname2'}]")
// orgService.getOrganizations = jest.fn().mockReturnValue('')
const result = orgService.getOrganizations()
expect(result).toBe(OrgRepo)
})
})
I see two issues in the way you are testing:
1.
You are trying to test an asynchronous method, and on your test, you are not waiting for this method to be finished before your expect statement.
A good test structure should be:
it('should test your method', (done) => {
const orgService = new OrganizationService();
const OrgRepo = jest.fn().mockReturnValue("[{_id: '123', name: 'testname'}, {_id: '456, name: 'testname2'}]")
orgService.getOrganizations()
.then((result) => {
expect(result).toEqual(OrgRepo); // I recommend using "toEqual" when comparing arrays
done();
});
})
Don't forget to put done as a parameter for your test!
You can find more about testing asynchronous functions on the Jest official documentation.
2.
In order to test your method properly, you want to isolate it from external dependencies. Here, the actual method OrganizationRepo.listAll() is called. You want to mock this method, for instance with a spy, so that you control its result and only test the getOrganizations method. That would look like this:
it('should test your method', (done) => {
const req = {
// Whatever structure it needs to be sure that the error in your method is not thrown
};
const orgService = new OrganizationService();
const orgRepoMock = spyOn(orgService['OrganizationRepo'], 'listAll')
.and.returnValue(Promise.resolve("[{_id: '123', name: 'testname'}, {_id: '456, name: 'testname2'}]"));
const OrgRepo = jest.fn().mockReturnValue("[{_id: '123', name: 'testname'}, {_id: '456, name: 'testname2'}]");
orgService.getOrganizations(req)
.then((result) => {
expect(result).toEqual(OrgRepo); // I recommend using "toEqual" when comparing arrays
expect(orgRepoMock).toHaveBeenCalled(); // For good measure
done();
});
})
This way, we make sure that your method is isolated and its outcome cannot be altered by external methods.
For this particular method, I also recommend that you test the error throwing depending on the input of your method.
I was able to answer this, first is I mocked the repository using
jest.mock('path/to/repo')
const mockGetOne = jest.fn()
OrganizationRepository.protorype.getOne = mockGetOne
then the rest is the test

How to test mongoose in NestJS Service?

I would like to test getFund() method from my service. I use NestJS that uses jest by default.
I have no idea how to test this line with jest: return await this.fundModel.findById(id);. Any idea?
import { Injectable } from '#nestjs/common';
import { Model } from 'mongoose';
import { Fund } from '../../funds/interfaces/fund.interface';
import { InjectModel } from '#nestjs/mongoose';
#Injectable()
export class FundService {
constructor(
#InjectModel('Fund')
private readonly fundModel: Model<Fund>,
) {}
/*****
SOME MORE CODE
****/
async getFund(id: string): Promise<Fund> {
return await this.fundModel.findById(id);
}
}
Edit
Thanks to slideshowp2 answer, I wrote this test.
describe('#getFund', () => {
it('should return a Promise of Fund', async () => {
let spy = jest.spyOn(service, 'getFund').mockImplementation(async () => {
return await Promise.resolve(FundMock as Fund);
});
service.getFund('');
expect(service.getFund).toHaveBeenCalled();
expect(await service.getFund('')).toEqual(FundMock);
spy.mockRestore();
});
});
The problem is that I get this result in my coverage report:
When I hover the line I get statement not covered.
There is only one statement return await this.fundModel.findById(id); in your getFund method. There is no other code logic which means the unit test you can do is only mock this.fundModel.findById(id) method and test
it .toBeCalledWith(someId).
We should mock each method and test the code logic in your getFund method. For now, there is no other code logic.
For example
async getFund(id: string): Promise<Fund> {
// we should mock this, because we should make an isolate environment for testing `getFund`
const fundModel = await this.fundModel.findById(id);
// Below branch we should test based on your mock value: fundModel
if(fundModel) {
return true
}
return false
}
Update
For example:
describe('#findById', () => {
it('should find ad subscription by id correctly', async () => {
(mockOpts.adSubscriptionDataSource.findById as jestMock).mockResolvedValueOnce({ adSubscriptionId: 1 });
const actualValue = await adSubscriptionService.findById(1);
expect(actualValue).toEqual({ adSubscriptionId: 1 });
expect(mockOpts.adSubscriptionDataSource.findById).toBeCalledWith(1);
});
});
The test coverage report:

Mock.mockImplementation() not working

I have a service class
Service.js
class Service {
}
export default new Service();
And I am trying to provide a mock implementation for this. If I use something like this:
jest.mock('./Service', () => { ... my mock stuff });
It works fine, however I'm not able to access any variables declared outside of the mock, which is a bit limiting as I'd like to reconfigure what the mock returns, etc.
I tried this (inspired by this other StackOverflow article: Service mocked with Jest causes "The module factory of jest.mock() is not allowed to reference any out-of-scope variables" error)
import service from './Service';
jest.mock('./Service', () => jest.fn);
service.mockImplementation(() => {
return { ... mock stuff }
);
Unfortunately when I am trying to run this, I get the below error:
TypeError: _Service2.default.mockImplementation is not a function
I had same problem as #Janos, the other answers didn't help either. You could do two things :
If you need to mock only a function from Service, in your test file:
import service from './Service';
jest.mock('./Service', () => jest.fn());
service.yourFunction = jest.fn(() => { /*your mock*/ })
 
If you need to mock the entire Module:
Say your service.js is in javascript/utils, create a javascript/utils/_mocks_ and inside it create a service.js file, you can then mock the entire class in this file, eg:
const myObj = {foo: "bar"}
const myFunction1 = jest.fn(() => { return Promise.resolve(myObj) })
const myFunction2 = ...
module.exports = {
myFunction1,
myFunction2
}
then in your test file you just add:
jest.mock('./javascript/utils/service')
...functions exported from the mockfile will be then hit through your test file execution.
The mock is equal to jest.fn. You need to call jest.fn to create a mocked function.
So this:
jest.mock('./Service', () => jest.fn);
Should be:
jest.mock('./Service', () => jest.fn());
ran into similar issues and resolved it by using .mockImplementationOnce
jest.mock('./Service', () => jest.fn()
.mockImplementationOnce(() => {
return { ... mock stuff }
})
.mockImplementationOnce(() => {
return { ... mock other stuff }
})
);
now when you run another test it will return the second mock object.
You need to store your mocked component in a variable with a name prefixed by "mock" and make sure you return an object with a default property as you import your Service from the default in your "main.js" file.
// Service.js
class Service {
}
export default new Service();
// main.test.js (main.js contains "import Service from './Service';")
const mockService = () => jest.fn();
jest.mock('./Service', () => {
return {
default: mockService
}
});
I had similar problem, and the cause was that ".spec.js" file had an
import jest from "jest-mock";
After removing this line, it worked.
My mistake was that I was resetting the mock before each test. If you do that, be sure to reconfigure the mock implementation.
For example, change this:
let value;
let onPropertyChange: OnPropertyChangeCallback = jest.fn((changes: any) => {
value = changes["testValue"];
});
const user = userEvent.setup();
beforeEach(() => {
jest.resetAllMocks();
});
to this:
let value;
let onPropertyChange: OnPropertyChangeCallback;
const user = userEvent.setup();
beforeEach(() => {
jest.resetAllMocks();
onPropertyChange = jest.fn((changes: any) => {
value = changes["testValue"];
});
});

Resources