How to unit test this prisma.service - unit-testing

I'm having trouble unit testing a prisma.service.ts file:
import { INestApplication, Injectable } from '#nestjs/common';
import { PrismaClient } from '#prisma/client';
#Injectable()
export class PrismaService extends PrismaClient {
async enableShutdownHooks(app: INestApplication) {
this.$on('beforeExit', async () => {
await app.close();
});
}
}
The prisma.service.spec.ts I have currently looks like this:
import { INestApplication } from '#nestjs/common';
import { NestFastifyApplication } from '#nestjs/platform-fastify';
import { Test, TestingModule } from '#nestjs/testing';
import { PrismaService } from './prisma.service';
const MockApp = jest.fn<Partial<INestApplication>, []>(() => ({
close: jest.fn(),
}));
describe('PrismaService', () => {
let service: PrismaService;
let app: NestFastifyApplication;
beforeEach(async () => {
app = MockApp() as NestFastifyApplication;
const module: TestingModule = await Test.createTestingModule({
providers: [PrismaService],
}).compile();
service = module.get<PrismaService>(PrismaService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
describe('enableShutdownHooks', () => {
it('should call $on and successfully close the app', async () => {
const spy = jest.spyOn(PrismaService.prototype, '$on')
.mockImplementation(async () => {
await app.close();
});
await service.enableShutdownHooks(app);
expect(spy).toBeCalledTimes(1);
expect(app.close).toBeCalledTimes(1);
spy.mockRestore();
});
});
});
However, this does not test line 8 of prisma.service.ts:
await app.close();
because I am mocking the implementation of this.$on('beforeExit', callback), with a copy of its original implementation.
Even if I don't mock it, app.close() never gets called.
Is there a way to test this line?

Could you try using a callback:
jest
.spyOn(service, '$on')
.mockImplementation(async (eventType, cb) => cb(() => Promise.resolve()))
await service.enableShutdownHooks(app);
expect(service.$on).toBeCalledTimes(1);
That allows you to use the callback to invoke the function where await app.close() is located.

Related

How to mock an imported function into a test suite in NestJs?

I want to write a unit test for my payment service but I'm receiving this error:
source.subscribe is not a function
at ./node_modules/rxjs/src/internal/lastValueFrom.ts:60:12
This is my service
import { HttpService } from '#nestjs/axios';
import { Injectable } from '#nestjs/common';
import { lastValueFrom } from 'rxjs';
import { PaymentInfo } from 'src/utils/types/paymentInfo';
#Injectable()
export class PaymentsService {
constructor(private readonly httpService: HttpService) {}
private createHeaderWithAuth(auth, contentType = 'application/json') {
return {
headers: {
authorization: auth.replace('Bearer', '').trim(),
'Content-Type': contentType,
},
};
}
async makePayment(auth: string, paymentInfo: PaymentInfo) {
const configs = this.createHeaderWithAuth(auth);
const response = await lastValueFrom(
await this.httpService.post(
`${process.env.PAYMENT_URL}/transaction/pay`,
paymentInfo,
configs
)
).catch((error) => {
console.log(error);
throw new Error(error.response.data.message);
});
return response.data;
}
}
So with a bit of searching and tinkering found out that this is caused by my import of a rxjs function to resolve the observable setted by axios.
I've searched ways to mock this function so I can properly test my service. But none of them gave me a solution, the questions i found only revolved around functions with modules, but these have none since is imported from a third party lib.
This is my test suite:
describe('Payments Service', () => {
let service: PaymentsService;
let mockedHttpService = {
post: jest
.fn()
.mockImplementation(
async (
url: string,
paymentInfo: PaymentInfo,
header = mockedHeader
) => {
return { mockedSuccessfulResponse };
}
),
get: jest
.fn()
.mockImplementation(async (url: string, header = mockedHeader) => {
return { ...mockedSuccessfulResponse, data: mockedUserCards };
}),
};
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
PaymentsService,
{
provide: HttpService,
useValue: mockedHttpService,
},
],
}).compile();
service = module.get<PaymentsService>(PaymentsService);
});
describe('Initialize', () => {
it('should define service', () => {
expect(service).toBeDefined();
});
describe('makePayment', () => {
it('should make a payment', async () => {
const payment = await service.makePayment(mockedAuth, mockedPaymentInfo);
expect(mockedHttpService.post).toHaveBeenCalledWith(
`${process.env.PAYMENT_URL}/transaction/pay`,
mockedPaymentInfo,
mockedHeader
);
expect(payment).toBe(mockedSuccessfulResponse);
});
});
});
Ps.: I removed the mocked objects to reduce the amount of code to read
you should use the of operator from rxjs, and drop the async keyword. Like:
.mockImplementation(
(
url: string,
paymentInfo: PaymentInfo,
header = mockedHeader
) => {
return of({ mockedSuccessfulResponse });
}
otherwise lastValueFrom won't receive an observable object.

Unit testing function in a class that injects EntityManager

I need help adding unit test to the function below in NestJs.
I have a class with a createOrder function as shown below. the constructor of the class injects an Entity Manager. How can I test for the createOrder function in jest.
import { Injectable } from '#nestjs/common';
import * as shortId from 'shortid';
import { EntityManager, Repository } from 'typeorm';
import { HttpException, HttpStatus } from '#nestjs/common';
import { Service } from 'models/service.model';
#Injectable()
export class OrderService {
private readonly orderRepository: Repository<Service>;
constructor(private readonly entityManager: EntityManager) {
this.orderRepository = entityManager.getRepository(Service);
}
async createOrder(data) {
const orderService = new Service();
orderService.id = shortId.generate(); // just to generate a string for id
const orderServiceData = Object.assign(orderService, data);
try {
await this.orderRepository.save(orderServiceData);
return { success: true };
} catch (err) {
throw new HttpException('Post not found', HttpStatus.NOT_FOUND);
}
}
}
This is what I have tried so far. Yet it fails to call the save function
import { Test, TestingModule } from '#nestjs/testing';
import { OrderService } from './order_service.service';
import { Service } from '../../models/service.model';
import { Repository, EntityManager, getRepository } from 'typeorm';
import { getRepositoryToken } from '#nestjs/typeorm';
describe('Order Service', () => {
let orderService: OrderServiceService;
let orderRepository: Repository<Service>;
const mockOrderRepository = () => ({
save: jest.fn(),
});
const mockEntityManager = () => ({
getRepository: jest.fn(),
});
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
OrderService,
{
provide: EntityManager,
useFactory: mockEntityManager,
},
{
provide: getRepositoryToken(Service),
useFactory: mockOrderRepository,
},
],
}).compile();
orderService = await module.get<OrderService>(
OrderService,
);
orderRepository = await module.get(getRepositoryToken(Service));
});
it('should check that order service is defined', () => {
expect(orderService).toBeDefined();
});
describe('Create order service', () => {
it('should create an order service', () => {
expect(orderRepository.save).not.toHaveBeenCalled();
const data = {
name: 'Gucci Cloths',
type: 'Cloths',
};
orderService.createOrder(data);
expect(orderRepository.save).toHaveBeenCalled();
});
});
});
What you can do is mocking the save function of the orderRepository:
const mockRepository = {
save: jest.fn(),
}
const mockEntityManager = () => ({
getRepository: () => mockRepository,
});
This way you can test the function and also check that the save function has been called with the right parameters.

Nestjs unit-test - mock method guard

I have started to work with NestJS and have a question about mocking guards
for unit-test.
I'm trying to test a basic HTTP controller that has a method Guard attach to it.
My issue started when I injected a service to the Guard (I needed the ConfigService for the Guard).
When running the test the DI is unable to resolve the Guard
● AppController › root › should return "Hello World!"
Nest can't resolve dependencies of the ForceFailGuard (?). Please make sure that the argument at index [0] is available in the _RootTestModule context.
My force fail Guard:
import { Injectable, CanActivate, ExecutionContext } from '#nestjs/common';
import { ConfigService } from './config.service';
#Injectable()
export class ForceFailGuard implements CanActivate {
constructor(
private configService: ConfigService,
) {}
canActivate(context: ExecutionContext) {
return !this.configService.get().shouldFail;
}
}
Spec file:
import { CanActivate } from '#nestjs/common';
import { Test, TestingModule } from '#nestjs/testing';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ForceFailGuard } from './force-fail.guard';
describe('AppController', () => {
let appController: AppController;
beforeEach(async () => {
const mock_ForceFailGuard = { CanActivate: jest.fn(() => true) };
const app: TestingModule = await Test
.createTestingModule({
controllers: [AppController],
providers: [
AppService,
ForceFailGuard,
],
})
.overrideProvider(ForceFailGuard).useValue(mock_ForceFailGuard)
.overrideGuard(ForceFailGuard).useValue(mock_ForceFailGuard)
.compile();
appController = app.get<AppController>(AppController);
});
describe('root', () => {
it('should return "Hello World!"', () => {
expect(appController.getHello()).toBe('Hello World!');
});
});
});
I wasn't able to find examples or documentation on this issues. Am i missing something or is this a real issue ?
Appreciate any help,
Thanks.
There are 3 issues with the example repo provided:
There is a bug in Nestjs v6.1.1 with .overrideGuard() - see https://github.com/nestjs/nest/issues/2070
I have confirmed that its fixed in 6.5.0.
ForceFailGuard is in providers, but its dependency (ConfigService) is not available in the created TestingModule.
If you want to mock ForceFailGuard, simply remove it from providers and let .overrideGuard() do its job.
mock_ForceFailGuard had CanActivate as a property instead of canActivate.
Working example (nestjs v6.5.0):
import { CanActivate } from '#nestjs/common';
import { Test, TestingModule } from '#nestjs/testing';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ForceFailGuard } from './force-fail.guard';
describe('AppController', () => {
let appController: AppController;
beforeEach(async () => {
const mock_ForceFailGuard: CanActivate = { canActivate: jest.fn(() => true) };
const app: TestingModule = await Test
.createTestingModule({
controllers: [AppController],
providers: [
AppService,
],
})
.overrideGuard(ForceFailGuard).useValue(mock_ForceFailGuard)
.compile();
appController = app.get<AppController>(AppController);
});
describe('root', () => {
it('should return "Hello World!"', () => {
expect(appController.getHello()).toBe('Hello World!');
});
});
});
If you ever need/want to unit test your custom guard implementation in addition to the controller unit test, you could have something similar to the test below in order to expect for errors etc
// InternalGuard.ts
#Injectable()
export class InternalTokenGuard implements CanActivate {
constructor(private readonly config: ConfigService) {
}
public async canActivate(context: ExecutionContext): Promise<boolean> {
const token = this.config.get("internalToken");
if (!token) {
throw new Error(`No internal token was provided.`);
}
const request = context.switchToHttp().getRequest();
const providedToken = request.headers["authorization"];
if (token !== providedToken) {
throw new UnauthorizedException();
}
return true;
}
}
And your spec file
// InternalGuard.spec.ts
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [],
providers: [
InternalTokenGuard,
{
provide: ConfigService,
useValue: {
get: jest.fn((key: string) => {
if (key === "internalToken") {
return 123;
}
return null;
})
}
}
]
}).compile();
config = module.get<ConfigService>(ConfigService);
guard = module.get<InternalTokenGuard>(InternalTokenGuard);
});
it("should throw UnauthorizedException when token is not Bearer", async () => {
const context = {
getClass: jest.fn(),
getHandler: jest.fn(),
switchToHttp: jest.fn(() => ({
getRequest: jest.fn().mockReturnValue({
headers: {
authorization: "providedToken"
}
})
}))
} as any;
await expect(guard.canActivate(context)).rejects.toThrow(
UnauthorizedException
);
expect(context.switchToHttp).toHaveBeenCalled();
});

Async Jest test in vueJS lifecycle hook with Interval

I would to understand how can i test my "auth/refresh" action in the "beforeCreate" hook with jest like below :
// main.vue
async beforeCreate() {
let authTokenRefreshIntervalId;
await this.$store.dispatch('auth/initialize');
authTokenRefreshIntervalId = setInterval(() => {
this.$store.dispatch('auth/refresh').catch(() => {
this.$store.dispatch('auth/logout');
clearInterval(authTokenRefreshIntervalId);
});
}, 30 * 1000);
}
// main.spec.js
import Vue from 'vue';
import Vuex from 'vuex';
import { shallow, createLocalVue, mount } from '#vue/test-utils';
import Main from '#/main';
const localVue = createLocalVue();
jest.useFakeTimers();
describe('store-auth', () => {
let store;
let actions;
let getters;
beforeEach(() => {
actions = {
initialize: jest.fn(),
refresh: jest.fn(),
logout: jest.fn(),
};
getters = {
isAuthenticated: jest.fn(),
};
store = new Vuex.Store({
modules: {
auth: {
namespaced: true,
actions,
getters,
},
},
});
});
it('dispatch initialize on beforeCreate hook', () => {
const wrapper = shallow(Main, { store, localVue });
expect(actions.initialize).toHaveBeenCalled();
});
it('dispatch refresh on beforeCreate hook every 30s', () => {
const wrapper = shallow(Main, { store, localVue });
jest.runTimersToTime(30 * 1000);
expect(actions.refresh).toHaveBeenCalled();
});
});
Jest say that the mocked function is not called.
I tried with expect(setInterval).toHaveBeenCalled() and it pass the test.
Where i'm wrong plz ?
Try using async/await in your test also.
it('dispatch refresh on beforeCreate hook every 30s', async () => {
const wrapper = shallow(Main, { store, localVue });
jest.runTimersToTime(30 * 1000);
await expect(actions.refresh).toHaveBeenCalled();
});

Axios Mock Adapter waiting for timeout before dispatching?

In my React Redux app, I tried to do some tests to one of my function that has settimeout in it
// MyFunc.js
export function updateSomething(id, data) {
return (dispatch, getState) => {
dispatch({type: 'dispatch1'})
settimeout(() => {
axios.get('/data')
.then((res) => {
dispatch({type: 'timeoutted_dispatch', data: res.data})
})
.catch(error => {
dispatch({type: 'dispatch_error'});
})
},3000);
dispatch({type: 'dispatch_end'});
}
}
// MyTest.js
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';;
import MockAdapter from 'axios-mock-adapter';
import axios from 'axios';
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
describe('Lets do some tests', () => {
it('Should dispatch timeoutted function', () => {
const store = mockStore({});
// Get all the dispatches for testing purpose
store.dispatch(updateSomething(1, {test: 'test'}))
.then(() => {
const actionList = store.getAction();
expect(actionList).toEqual(allDispatches);
// At this point, I won't be able to get anything inside timeout function
})
});
})
How do I set some kind of Await so I can wait for the timeout before trying to retrieve the data ?