This is my controller class(usercontoller.ts) i am trying to write junit test cases for this class
import { UpsertUserDto } from '../shared/interfaces/dto/upsert-user.dto';
import { UserDto } from '../shared/interfaces/dto/user.dto';
import { UserService } from './user.service';
async updateUser(#BodyToClass() user: UpsertUserDto): Promise<UpsertUserDto> {
try {
if (!user.id) {
throw new BadRequestException('User Id is Required');
}
return await this.userService.updateUser(user);
} catch (e) {
throw e;
}
}
This is my TestClass(UserContollerspec.ts)
while running my test classes getting error " Cannot spy the updateUser property because it is not a function; undefined given instead.
getting error.
However, when I use spyOn method, I keep getting TypeError: Cannot read property 'updateuser' of undefined:
*it seems jest.spyOn() not working properly where i am doing mistake.
could some one please help me.the argument which I am passing ?
jest.mock('./user.service');
describe('User Controller', () => {
let usercontroller: UserController;
let userservice: UserService;
// let fireBaseAuthService: FireBaseAuthService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [UserController],
providers: [UserService]
}).compile();
usercontroller = module.get<UserController>(UserController);
userservice = module.get<UserService>(UserService);
});
afterEach(() => {
jest.resetAllMocks();
});
describe('update user', () => {
it('should return a user', async () => {
//const result = new UpsertUserDto();
const testuser = new UpsertUserDto();
const mockDevice = mock <Promise<UpsertUserDto>>();
const mockNumberToSatisfyParameters = 0;
//const userservice =new UserService();
//let userservice: UserService;
jest.spyOn(userservice, 'updateUser').mockImplementation(() => mockDevice);
expect(await usercontroller.updateUser(testuser)).toBe(mockDevice);
it('should throw internal error if user not found', async (done) => {
const expectedResult = undefined;
****jest.spyOn(userservice, 'updateUser').mockResolvedValue(expectedResult);****
await usercontroller.updateUser(testuser)
.then(() => done.fail('Client controller should return NotFoundException error of 404 but did not'))
.catch((error) => {
expect(error.status).toBe(503);
expect(error.message).toMatchObject({error: 'Not Found', statusCode: 503}); done();
});
});
});
});
More than likely, your UserService class has other dependencies and as such, Nest cannot instantiate the UserService class. When you are trying to do userService = module.get(UserService) you are retrieving an undefined hence the error about jest.spyOn(). In unit tests, you should be providing a mock provider to take the place of your actual provider, like so:
describe("User Controller", () => {
let usercontroller: UserController;
let userservice: UserService;
// let fireBaseAuthService: FireBaseAuthService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [UserController],
providers: [
{
provide: UserService,
useValue: {
updateUser: jest.fn(),
// other UserService methods
}
}
],
}).compile();
usercontroller = module.get<UserController>(UserController);
userservice = module.get<UserService>(UserService);
});
// rest of tests
});
Now when you retrieve the UserService you'll have an object with the proper functions back, which can then be jest.spyOned and mocked
I have created following service to use twilio send login code sms to users:
sms.service.ts
import { Injectable, Logger } from '#nestjs/common';
import * as twilio from 'twilio';
Injectable()
export class SmsService {
private twilio: twilio.Twilio;
constructor() {
this.twilio = this.getTwilio();
}
async sendLoginCode(phoneNumber: string, code: string): Promise<any> {
const smsClient = this.twilio;
const params = {
body: 'Login code: ' + code,
from: process.env.TWILIO_SENDER_NUMBER,
to: phoneNumber
};
smsClient.messages.create(params).then(message => {
return message;
});
}
getTwilio() {
return twilio(process.env.TWILIO_SID, process.env.TWILIO_SECRET);
}
}
sms.service.spec.js that contains my test
import { Test, TestingModule } from '#nestjs/testing';
import { SmsService } from './sms.service';
import { Logger } from '#nestjs/common';
describe('SmsService', () => {
let service: SmsService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [SmsService]
}).compile();
service = module.get<SmsService>(SmsService);
});
describe('sendLoginCode', () => {
it('sends login code', async () => {
const mockMessage = {
test: "test"
}
jest.mock('twilio')
const twilio = require('twilio');
twilio.messages = {
create: jest.fn().mockImplementation(() => Promise.resolve(mockMessage))
}
expect(await service.sendLoginCode("4389253", "123456")).toBe(mockMessage);
});
});
});
How can I use jest create mock of the SmsService constructor so that it's twilio variable gets set to mocked version of it I create in service.spec.js?
You should inject your dependency instead of using it directly, then you can mock it in your test:
Create a custom provider
#Module({
providers: [
{
provide: 'Twillio',
useFactory: async (configService: ConfigService) =>
twilio(configService.TWILIO_SID, configService.TWILIO_SECRET),
inject: [ConfigService],
},
]
Inject it in your service
constructor(#Inject('Twillio') twillio: twilio.Twilio) {}
Mock it in your test
const module: TestingModule = await Test.createTestingModule({
providers: [
SmsService,
{ provide: 'Twillio', useFactory: twillioMockFactory },
],
}).compile();
See this thread on how to create mocks.
I am developing a node.js application using nestjs
I have a class called LoggerService as below
export class LoggerService {
private logger: Rollbar;
constructor() {
this.logger = this.setupLogger();
}
critical(...args: Array<string | Error | object | Date | any[]>) {
this.logger.error(...args);
}
private setupLogger(): Rollbar {
if (this.logger == null) {
this.logger = new Rollbar({
accessToken: 'token',
environment: 'dev',
captureUncaught: true,
captureUnhandledRejections: true,
});
}
return this.logger;
}
Now I am writing unit test for this class using jest as below.
describe('LoggerService.log', () => {
let service: LoggerService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [LoggerService],
}).compile();
service = module.get<LoggerService>(LoggerService);
});
it('critical', () => {
service.critical('error','message');
expect(???).toHaveBeenCalledWith('error', 'message')
})
);
My question is how to check(expect) if logger.error is called, or how to mock Rollbar in this class.
1) Provide your external dependency/library as an injectable token in your module
#Module({
providers: [
{
provide: 'Rollbar',
useFactory: async (configService: ConfigService) => new Rollbar({
accessToken: configService.accessToken,
environment: configService.environment,
captureUncaught: true,
captureUnhandledRejections: true,
}),
inject: [ConfigService],
},
]
2) Inject it in your LoggerService instead of creating it
export class LoggerService {
constructor(#Inject('Rollbar') private logger: Rollbar) {
}
3) Now you can mock your dependency in your test
const module: TestingModule = await Test.createTestingModule({
providers: [
LoggerService,
{ provide: 'Rollbar', useFactory: rollbarMockFactory },
],
}).compile();
There's a longish discussion about how to do this in this issue.
I've experimented with a number of the proposed solutions but I'm not having much luck.
Could anyone provide a concrete example of how to test a service with an injected repository and mock data?
Let's assume we have a very simple service that finds a user entity by id:
export class UserService {
constructor(#InjectRepository(UserEntity) private userRepository: Repository<UserEntity>) {
}
async findUser(userId: string): Promise<UserEntity> {
return this.userRepository.findOne(userId);
}
}
Then you can mock the UserRepository with the following mock factory (add more methods as needed):
// #ts-ignore
export const repositoryMockFactory: () => MockType<Repository<any>> = jest.fn(() => ({
findOne: jest.fn(entity => entity),
// ...
}));
Using a factory ensures that a new mock is used for every test.
describe('UserService', () => {
let service: UserService;
let repositoryMock: MockType<Repository<UserEntity>>;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
UserService,
// Provide your mock instead of the actual repository
{ provide: getRepositoryToken(UserEntity), useFactory: repositoryMockFactory },
],
}).compile();
service = module.get<UserService>(UserService);
repositoryMock = module.get(getRepositoryToken(UserEntity));
});
it('should find a user', async () => {
const user = {name: 'Alni', id: '123'};
// Now you can control the return value of your mock's methods
repositoryMock.findOne.mockReturnValue(user);
expect(service.findUser(user.id)).toEqual(user);
// And make assertions on how often and with what params your mock's methods are called
expect(repositoryMock.findOne).toHaveBeenCalledWith(user.id);
});
});
For type safety and comfort you can use the following typing for your (partial) mocks (far from perfect, there might be a better solution when jest itself starts using typescript in the upcoming major releases):
export type MockType<T> = {
[P in keyof T]?: jest.Mock<{}>;
};
My solution uses sqlite memory database where I insert all the needed data and create schema before every test run. So each test counts with the same set of data and you do not have to mock any TypeORM methods:
import { Test, TestingModule } from "#nestjs/testing";
import { CompanyInfo } from '../../src/company-info/company-info.entity';
import { CompanyInfoService } from "../../src/company-info/company-info.service";
import { Repository, createConnection, getConnection, getRepository } from "typeorm";
import { getRepositoryToken } from "#nestjs/typeorm";
describe('CompanyInfoService', () => {
let service: CompanyInfoService;
let repository: Repository<CompanyInfo>;
let testingModule: TestingModule;
const testConnectionName = 'testConnection';
beforeEach(async () => {
testingModule = await Test.createTestingModule({
providers: [
CompanyInfoService,
{
provide: getRepositoryToken(CompanyInfo),
useClass: Repository,
},
],
}).compile();
let connection = await createConnection({
type: "sqlite",
database: ":memory:",
dropSchema: true,
entities: [CompanyInfo],
synchronize: true,
logging: false,
name: testConnectionName
});
repository = getRepository(CompanyInfo, testConnectionName);
service = new CompanyInfoService(repository);
return connection;
});
afterEach(async () => {
await getConnection(testConnectionName).close()
});
it('should be defined', () => {
expect(service).toBeDefined();
});
it('should return company info for findOne', async () => {
// prepare data, insert them to be tested
const companyInfoData: CompanyInfo = {
id: 1,
};
await repository.insert(companyInfoData);
// test data retrieval itself
expect(await service.findOne()).toEqual(companyInfoData);
});
});
I got inspired here: https://gist.github.com/Ciantic/be6a8b8ca27ee15e2223f642b5e01549
Similar to best practices in other frameworks you can use a test DB instead of a mock.
describe('EmployeesService', () => {
let employeesService: EmployeesService;
let moduleRef: TestingModule;
beforeEach(async () => {
moduleRef = await Test.createTestingModule({
imports: [
TypeOrmModule.forRoot({
type: 'postgres',
url: 'postgres://postgres:#db:5432/test', // read this from env
autoLoadEntities: true,
synchronize: true,
dropSchema: true,
}),
],
providers: [EmployeesService],
}).compile();
employeesService = moduleRef.get<EmployeesService>(EmployeesService);
});
afterEach(async () => {
await moduleRef.close();
});
describe('findOne', () => {
it('returns empty array', async () => {
expect(await employeesService.findAll()).toStrictEqual([]);
});
});
});
Real-life example in resolver specs in: https://github.com/thisismydesign/nestjs-starter
Last tested with typeorm#0.3.7 and #nestjs/typeorm#9.0.0.
I also found that this worked for me:
export const mockRepository = jest.fn(() => ({
metadata: {
columns: [],
relations: [],
},
}));
and
const module: TestingModule = await Test.createTestingModule({
providers: [{ provide: getRepositoryToken(Entity), useClass: mockRepository }],
}).compile();
Starting with the above ideas and to help with mocking any class, we came out with this MockFactory:
export type MockType<T> = {
[P in keyof T]?: jest.Mock<unknown>;
};
export class MockFactory {
static getMock<T>(type: new (...args: any[]) => T, includes?: string[]): MockType<T> {
const mock: MockType<T> = {};
Object.getOwnPropertyNames(type.prototype)
.filter((key: string) => key !== 'constructor' && (!includes || includes.includes(key)))
.map((key: string) => {
mock[key] = jest.fn();
});
return mock;
}
}
const module: TestingModule = await Test.createTestingModule({
providers: [
{
provide: getRepositoryToken(MyCustomRepository),
useValue: MockFactory.getMock(MyCustomRepository)
}
]
}).compile();
First of all I'm new to Ts/Js/Node.
Here is my example code : it lets you use NEST's injection system with a custom Connection during tests.
In this manner service/controller objects are not created by hand but wired by the TestingModule:
import { Test } from '#nestjs/testing';
import { getRepositoryToken } from '#nestjs/typeorm';
import {
Repository,
createConnection,
getConnection,
getRepository,
Connection,
} from 'typeorm';
import { Order } from './order';
import { OrdersService } from './orders.service';
describe('Test Orders', () => {
let repository: Repository<Order>;
let service: OrdersService;
let connection: Connection;
beforeEach(async () => {
connection = await createConnection({
type: 'sqlite',
database: './test.db',
dropSchema: true,
entities: [Order],
synchronize: true,
logging: true,
});
repository = getRepository(Order);
const testingModule = await Test.createTestingModule({
providers: [
OrdersService,
{
provide: getRepositoryToken(Order, connection),
useFactory: () => {
return repository;
},
},
],
}).compile();
console.log('Getting Service from NEST');
service = testingModule.get<OrdersService>(OrdersService);
return connection;
});
afterEach(async () => {
await getConnection().close();
});
it('should be defined', () => {
expect(service).toBeDefined();
});
it('CRUD Order Test', async () => {
const order = new Order();
order.currency = 'EURO';
order.unitPrice = 12.0;
order.issueDate = new Date();
const inserted = await service.create(order);
console.log('Inserted order ', inserted.id); // id is the #PrimaryGeneratedColumn() key
let allOrders = await service.findAll();
expect(allOrders.length).toBe(1);
await service.delete(inserted.id);
allOrders = await service.findAll();
expect(allOrders.length).toBe(0);
});
});
Something similar to the suggested MockTypes defined in the previous answer is the TypedMockType
type ArgsType<T> = T extends (...args: infer A) => unknown ? A : never;
export type TypedMockType<T> = {
// eslint-disable-next-line #typescript-eslint/no-explicit-any
[P in keyof T]: T[P] extends (...args: any) => unknown
? jest.Mock<ReturnType<T[P]>, ArgsType<T[P]>>
: never;
};
This is a utility type that can be used the same as MockType, but the difference is that your payloads of the original method signature will be the same.
api.controller.ts
#Controller('api')
export class ApiController {
constructor() {}
#Post()
#Transaction()
async root(#Req() req: Request, #Res() res: Response, #TransactionManager() manager: EntityManager): Promise<void> {
res.send(/* anything */);
}
}
api.e2e-spec.ts
describe('API (e2e)', () => {
let app: INestApplication;
let connection: Connection;
beforeAll(async () => {
const module = await Test.createTestingModule({
imports: [],
controllers: [ApiController],
providers: [],
})
.overrideProvider('Connection')
.useValue(/** How to ??? */)
.compile();
app = module.createNestApplication();
await app.init();
});
});
Result is
[Nest] 35181 - 10/30/2018, 5:42:06 PM [ExceptionHandler]
Connection "default" was not found.
ConnectionNotFoundError: Connection "default" was not found.
In this case, I just want to check request parameters and response values using mock by test.
I'd like to disable the DB connection by override it.
Well assuming you have some kind of a mocked Connection (e.g. use jest.createMockInstance see https://www.npmjs.com/package/jest-create-mock-instance ):
api.e2e-spec.ts
describe('API (e2e)', () => {
let app: INestApplication;
let connection: Mocked<Connection>;
beforeAll(async () => {
connection = createMockInstance(Connection);
const module = await Test.createTestingModule({
imports: [],
controllers: [ApiController],
providers: [],
})
.overrideProvider(Connection)
.useValue(connection)
.compile();
app = module.createNestApplication();
await app.init();
});
});