NestJS provides a sample Transaction code on (https://docs.nestjs.com/techniques/database#transactions), and I now would like to create Unit test script against the code. Here are the some dependent files:
#Entity()
export class User {
#PrimaryGeneratedColumn()
id: number;
#Column({type: 'text', name: 'first_name'})
firstName: string;
#Column({type: 'text', name: 'last_name'})
lastName: string;
#Column({name: 'is_active', default: true})
isActive: boolean;
}
#Injectable()
export class UsersService {
constructor(
private connection: Connection
) {}
async createMany(users: User[]): User[] {
const queryRunner = this.connection.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try {
const user1 = await queryRunner.manager.save(users[0]);
await queryRunner.commitTransaction();
return [user1];
} catch (err) {
// since we have errors lets rollback the changes we made
await queryRunner.rollbackTransaction();
} finally {
// you need to release a queryRunner which was manually instantiated
await queryRunner.release();
}
}
}
Here is the unit test script. I am close to accomplish but still I am getting undefined from await queryRunner.manager.save(users[0]) with the jest.spyOn setup below:
describe('UsersService', () => {
let usersService: UsersService;
let connection: Connection;
class ConnectionMock {
createQueryRunner(mode?: "master" | "slave"): QueryRunner {
const qr = {
manager: {},
} as QueryRunner;
qr.manager;
Object.assign(qr.manager, {
save: jest.fn()
});
qr.connect = jest.fn();
qr.release = jest.fn();
qr.startTransaction = jest.fn();
qr.commitTransaction = jest.fn();
qr.rollbackTransaction = jest.fn();
qr.release = jest.fn();
return qr;
}
}
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [UsersService, {
provide: Connection,
useClass: ConnectionMock,
}],
}).compile();
usersService = module.get<UsersService>(UsersService);
connection = module.get<Connection>(Connection);
});
it('should be defined', () => {
expect(usersService).toBeDefined();
});
it('should return expected results after user create', async () => {
const user1: User = { id: 1, firstName: 'First', lastName: 'User', isActive: true };
const queryRunner = connection.createQueryRunner();
// I would like to make the following spyOn work
jest.spyOn(queryRunner.manager, 'save').mockResolvedValueOnce(user1);
expect(await appService.createMany([user1])).toEqual([user1]);
});
});
connection.createQueryRunner(); will return difference instance of QueryRunner.
This mean queryRunner of const queryRunner = connection.createQueryRunner(); will not reference to queryRunner in const queryRunner = this.connection.createQueryRunner(); of createMany function.
Then your mock jest.spyOn(queryRunner.manager, 'save').mockResolvedValueOnce(user1); does not make sense.
Keep it simple, make QueryRunner as a "global" variable, then binding it to ConnectionMock, this mean we will have the same "variable" when we call createQueryRunner.
Updated beforeEach content, ConnectionMock class.
describe('UsersService', () => {
let usersService: UsersService;
let connection: Connection;
const qr = {
manager: {},
} as QueryRunner;
class ConnectionMock {
createQueryRunner(mode?: "master" | "slave"): QueryRunner {
return qr;
}
}
beforeEach(async () => {
// reset qr mocked function
Object.assign(qr.manager, {
save: jest.fn()
});
qr.connect = jest.fn();
qr.release = jest.fn();
qr.startTransaction = jest.fn();
qr.commitTransaction = jest.fn();
qr.rollbackTransaction = jest.fn();
qr.release = jest.fn();
const module: TestingModule = await Test.createTestingModule({
providers: [UsersService, {
provide: Connection,
useClass: ConnectionMock,
}],
}).compile();
usersService = module.get<UsersService>(UsersService);
connection = module.get<Connection>(Connection);
});
it('should be defined', () => {
expect(usersService).toBeDefined();
});
it('should return expected results after user create', async () => {
const user1: User = { id: 1, firstName: 'First', lastName: 'User', isActive: true };
const queryRunner = connection.createQueryRunner();
// I would like to make the following spyOn work
jest.spyOn(queryRunner.manager, 'save').mockResolvedValueOnce(user1);
expect(await appService. createMany([user1])).toEqual([user1]);
});
});
Related
I am using NestJs framework in my project. Here i want to write testcase for a service file. But i am facing an issue in feeding the value of this.userPool in the service file.I am getting userPoolId and clientId undefined error.
I tried different solutions, but nothing works for me.
service.js
export class AuthService {
private userPool: CognitoUserPool;
constructor(private readonly authConfig: AuthConfig) {
this.userPool = new CognitoUserPool({
UserPoolId: this.authConfig.userPoolId,
ClientId: this.authConfig.clientId,
});
}
authenticateUser(user: AuthCredentialsDto) {
try {
const { userName, password } = user;
const authenticateDetails = new AuthenticationDetails({
Username: userName,
Password: password,
});
const userData = {
Username: userName,
Pool: this.userPool,
};
const newUser = new CognitoUser(userData);
return new Promise((resolve, reject) => {
------------------------
--------------------------------
});
} catch (error) {
throw new BadRequestException(error.message);
}
}
service.spec.ts
import { AuthService } from './auth.service';
import {
AuthenticationDetails,
CognitoUser,
CognitoUserAttribute,
CognitoUserPool,
} from 'amazon-cognito-identity-js';
import { AuthConfig } from './cognito.service';
// const mockCognitoUserPool = () => ({
// registerUser: jest.fn(),
// });
describe('AuthService', () => {
let service: AuthService;
let authConfig: AuthConfig;
let userPool: CognitoUserPool;
userPool = new CognitoUserPool({
UserPoolId: authConfig.userPoolId,
ClientId: authConfig.clientId,
});
// let userPool;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
AuthService,
AuthConfig,
userPool
],
}).compile();
service = module.get<AuthService>(AuthService);
authConfig = module.get<AuthConfig>(AuthConfig);
// cognitoUserPool = module.get<CognitoUserPool>(CognitoUserPool);
});
describe('authenticateUser', () => {
it('calls registerUser and returns the result', async () => {
const mockUser = {
userName: 'usernme',
password: 'password',
};
// tasksRepository.findOne.mockResolvedValue(mockTask);
const result = await service.authenticateUser(mockUser);
expect(result).toEqual('result');
});
});
// it('should be defined', () => {
// expect(service).toBeDefined();
// });
});
Did you mean to spell your username property ‘usernme’ in the test file?
I'm working on NestJs application and wrote unit test for my authenticateUser function in user.service.ts.It's has pass in my local machine.but when I deployed it in to server and run unit test, i got an error Redis connection to 127.0.0.1:6379 failed - connect ECONNREFUSED.Seems like redis mock is not working.How should I mock redis and resolve this issue for working?
user.service.ts
async authenticateUser(authDto: AuthDTO): Promise<AuthResponse> {
try {
const userData = await this.userRepository.findOne({msisdn});
if(userData){
await this.redisCacheService.setCache(msisdn, userData);
}
} catch (error) {
console.log(error)
}
}
redisCache.service.ts
export class RedisCacheService {
constructor(
#Inject(CACHE_MANAGER) private readonly cache: Cache,
) {}
async setCache(key, value) {
await this.cache.set(key, value);
}
}
user.service.spec.ts
describe('Test User Service', () => {
let userRepository: Repository<UserEntity>;
let userService: UserService;
let redisCacheService: RedisCacheService;
let cacheManager: any;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
UserService,
UserEntity,
RedisCacheService,
{
provide: getRepositoryToken(UserEntity),
useClass: registeredApplicationRepositoryMockFactory,
},
],
imports: [CacheModule.register({})],
}).compile();
userService = module.get<UserService>(UserService);
userRepository = module.get<Repository<UserEntity>>(
getRepositoryToken(UserEntity),
);
redisCacheService = module.get<RedisCacheService>(RedisCacheService);
cacheManager = module.get<any>(CACHE_MANAGER);
});
it('authenticateUser should return success response', async () => {
const userEntity = { id: 1, name: 'abc', age: 25 };
const mockSuccessResponse = new AuthResponse(
HttpStatus.OK,
STRING.SUCCESS,
`${STRING.USER} ${STRING.AUTHENTICATE} ${STRING.SUCCESS}`,
{},
);
jest.spyOn(userRepository, 'findOne').mockResolvedValueOnce(userEntity);
jest.spyOn(redisCacheService, 'setCache').mockResolvedValueOnce(null);
expect(await userService.authenticateUser(mockAuthBody)).toEqual(mockSuccessResponse);
});
});
You can mock CACHE_MANAGER using a custom provider:
import { CACHE_MANAGER } from '#nestjs/common';
import { Cache } from 'cache-manager';
describe('AppService', () => {
let service: AppService;
let cache: Cache;
beforeEach(async () => {
const app = await Test.createTestingModule({
providers: [
AppService,
{
provide: CACHE_MANAGER,
useValue: {
get: () => 'any value',
set: () => jest.fn(),
},
},
],
})
.compile();
service = app.get<AppService>(AppService);
cache = app.get(CACHE_MANAGER);
});
// Then you can use jest.spyOn() to spy and mock
it(`should cache the value`, async () => {
const spy = jest.spyOn(cache, 'set');
await service.cacheSomething();
expect(spy).toHaveBeenCalledTimes(1);
expect(spy.mock.calls[0][0]).toEqual('key');
expect(spy.mock.calls[0][1]).toEqual('value');
});
it(`should get the value from cache`, async () => {
const spy = jest.spyOn(cache, 'get');
await service.getSomething();
expect(spy).toHaveBeenCalledTimes(1);
});
it(`should return the value from the cache`, async () => {
jest.spyOn(cache, 'get').mockResolvedValueOnce('value');
const res = await service.getSomething();
expect(res).toEqual('value');
}),
});
More details on Custom Providers: https://docs.nestjs.com/fundamentals/custom-providers
Two more things, for unit testing you shouldn't import modules but mock the dependencies instead. And as Daniel said, UserService is not using CACHE_MANAGER but RedisCacheService, so you should mock RedisCacheService.
Usually the best thing to do is to only provide the service you're testing and mock the dependencies.
in order to use the jest spy functions you need to return the jest function directly.
providers: [
AppService,
{
provide: CACHE_MANAGER,
useValue: {
get: () => 'any value',
set: jest.fn(),
},
},
],
I've the following code and I need to mock the connection.execute() function as this belongs to the third-party lib snowflake-sdk and is't not part of my unit test:
import * as SnowflakeSDK from 'snowflake-sdk';
import { Injectable } from '#nestjs/common';
#Injectable()
export class SnowflakeClient {
export(bucket: string, filename: string, query: string) {
return new Promise((resolve, reject) => {
const connection = this.getConnection();
const command = `COPY INTO '${bucket}${filename}' FROM (${query})`;
connection.execute({
sqlText: command,
complete: function (err) {
try {
if (err) {
return reject(err);
} else {
return resolve(true);
}
} finally {
connection.destroy(function (err) {
if (err) {
console.error('Unable to disconnect: ' + err.message);
}
});
}
},
});
});
}
getConnection() {
const connection = SnowflakeSDK.createConnection({
account: process.env.SNOWFLAKE_HOST!,
username: process.env.SNOWFLAKE_USERNAME!,
password: process.env.SNOWFLAKE_PASSWORD!,
role: process.env.SNOWFLAKE_ROLE!,
warehouse: process.env.SNOWFLAKE_WAREHOUSE!,
database: process.env.SNOWFLAKE_DATABASE!,
schema: process.env.SNOWFLAKE_SCHEMA!,
});
connection.connect(function (err: Error): void {
if (err) {
throw err;
}
});
return connection;
}
}
So I've created the following unit test:
import { SnowflakeClient } from 'src/snowflake/snowflake-client';
import { Test } from '#nestjs/testing';
describe('SnowflakeClient', () => {
let snowflakeClient: SnowflakeClient;
beforeEach(async () => {
const moduleRef = await Test.createTestingModule({
providers: [SnowflakeClient],
}).compile();
snowflakeClient = moduleRef.get<SnowflakeClient>(SnowflakeClient);
});
describe('export', () => {
it('should export a SQL query', async () => {
const connectionMock = jest.fn().mockImplementation(() => ({
execute: function (bucket: string, filename: string, sql: string) {
console.log(`${bucket}${filename}: ${sql}`);
},
}));
jest.spyOn(snowflakeClient, 'getConnection').mockImplementation(connectionMock);
const bucket = 's3://bucketName';
const filename = '/reports/test.csv';
const sql = 'select * from customers limit 100';
await expect(await snowflakeClient.export(bucket, filename, sql)).resolves.not.toThrow();
}, 10000);
});
});
But the connectionMock.execute isn't being called correctly as I'm getting the following error:
FAIL src/snowflake/snowflake-client.spec.ts (13.518 s)
SnowflakeClient
export
✕ should export a SQL query (10018 ms)
● SnowflakeClient › export › should export a SQL query
thrown: "Exceeded timeout of 10000 ms for a test.
Use jest.setTimeout(newTimeout) to increase the timeout value, if this is a long-running test."
14 |
15 | describe('export', () => {
> 16 | it('should export a SQL query', async () => {
| ^
17 | const connectionMock = jest.fn().mockImplementation(() => ({
18 | execute: function (bucket: string, filename: string, sql: string) {
19 | console.log(`${bucket}${filename}: ${sql}`);
at src/snowflake/snowflake-client.spec.ts:16:5
at src/snowflake/snowflake-client.spec.ts:15:3
at Object.<anonymous> (src/snowflake/snowflake-client.spec.ts:4:1)
I would like to test the SnowflakeClient.export() method but I need to mock the snowflake-sdk module as it isn't part of my code.
Anybody knows what I'm doing wrong?
When using sdk, it's better to pass them through the injection system of NestJs with a custom provider:
//////////////////////////////////
// 1. Provide the sdk in the module.
//////////////////////////////////
const SnowflakeConnectionProvider = {
provide: 'SNOWFLAKE_CONNECTION',
useFactory: () => {
const connection = SnowflakeSDK.createConnection({
account: process.env.SNOWFLAKE_HOST!,
username: process.env.SNOWFLAKE_USERNAME!,
password: process.env.SNOWFLAKE_PASSWORD!,
role: process.env.SNOWFLAKE_ROLE!,
warehouse: process.env.SNOWFLAKE_WAREHOUSE!,
database: process.env.SNOWFLAKE_DATABASE!,
schema: process.env.SNOWFLAKE_SCHEMA!,
});
connection.connect(function (err: Error): void {
if (err) {
throw err;
}
});
return connection;
},
inject: [],
}
#Module({
providers: [
SnowflakeClient,
SnowflakeConnectionProvider,
]
})
export class SnowflakeModule {}
//////////////////////////////////
// 2. Inject the connection into your `SnowflakeClient`
//////////////////////////////////
#Injectable()
export class SnowflakeClient {
constructor(#Inject('SNOWFLAKE_CONNECTION') private readonly connection: SnowflakeConnection) {}
// ...
}
//////////////////////////////////
// 3. Mock the dependency in tests
//////////////////////////////////
beforeEach(async () => {
const moduleRef = await Test.createTestingModule({
providers: [
{
provide: 'SNOWFLAKE_CONNECTION',
useValue: { /* Mock */},
},
SnowflakeClient,
],
}).compile();
snowflakeClient = moduleRef.get<SnowflakeClient>(SnowflakeClient);
});
You will have a much better control over the tests.
So I got this fixed doing this (but I'm not sure it's the best way):
const connectionMock = jest.createMockFromModule<Connection>('snowflake-sdk');
connectionMock.execute = jest.fn().mockImplementation(() => {
return Promise.resolve(null);
});
jest.spyOn(snowflakeClient, 'getConnection').mockImplementation(() => {
return connectionMock;
});
expect(snowflakeClient.export(bucket, filename, sql)).resolves.not.toThrow();
Let me know your thoughts :)
I'm testing a nest.js service, this method basically will receive a find method from sequelize:
...
import { Auth } from './../../modules/auth/entities/auth.entity';
import { Sequelize } from 'sequelize-typescript';
import { getModelToken } from '#nestjs/sequelize';
jest.mock('./../../modules/auth/auth.service');
const sequelize = new Sequelize({ validateOnly: true });
sequelize.addModels([Auth]);
const testAuth = new Auth({
id_user: '1',
});
describe('AuthGuardService', () => {
let service: AuthGuardService;
let authService: AuthService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
AuthGuardService,
AuthService,
{
provide: getModelToken(Auth),
useValue: { findOne: jest.fn(() => testAuth) },
},
],
imports: [HttpModule],
}).compile();
service = module.get<AuthGuardService>(AuthGuardService);
authService = module.get<AuthService>(AuthService);
});
...
it('it should return true', async () => {
expect(
await authService.find({
id: '1',
})
).toEqual(testAuth);
});
on my service I'm trying to test this exactly block:
const user = await this.authService.find({
id: value,
});
if(user.id === some_value) {
return true;
}
but I always get undefined when I tried to read user from my test.
TypeError: Cannot read property 'id' of undefined
I am testing the following service:
#Injectable()
export class TripService {
private readonly logger = new Logger('TripService');
constructor(
#InjectRepository(TripEntity)
private tripRepository: Repository<TripEntity>
) {}
public async showTrip(clientId: string, tripId: string): Promise<Partial<TripEntity>> {
const trip = await this.tripRepository
.createQueryBuilder('trips')
.innerJoinAndSelect('trips.driver', 'driver', 'driver.clientId = :clientId', { clientId })
.where({ id: tripId })
.select([
'trips.id',
'trips.distance',
'trips.sourceAddress',
'trips.destinationAddress',
'trips.startTime',
'trips.endTime',
'trips.createdAt'
])
.getOne();
if (!trip) {
throw new HttpException('Trip not found', HttpStatus.NOT_FOUND);
}
return trip;
}
}
My repository mock:
export const repositoryMockFactory: () => MockType<Repository<any>> = jest.fn(() => ({
findOne: jest.fn(entity => entity),
findAndCount: jest.fn(entity => entity),
create: jest.fn(entity => entity),
save: jest.fn(entity => entity),
update: jest.fn(entity => entity),
delete: jest.fn(entity => entity),
createQueryBuilder: jest.fn(() => ({
delete: jest.fn().mockReturnThis(),
innerJoinAndSelect: jest.fn().mockReturnThis(),
innerJoin: jest.fn().mockReturnThis(),
from: jest.fn().mockReturnThis(),
where: jest.fn().mockReturnThis(),
execute: jest.fn().mockReturnThis(),
getOne: jest.fn().mockReturnThis(),
})),
}));
My tripService.spec.ts:
import { Test, TestingModule } from '#nestjs/testing';
import { TripService } from './trip.service';
import { MockType } from '../mock/mock.type';
import { Repository } from 'typeorm';
import { TripEntity } from './trip.entity';
import { getRepositoryToken } from '#nestjs/typeorm';
import { repositoryMockFactory } from '../mock/repositoryMock.factory';
import { DriverEntity } from '../driver/driver.entity';
import { plainToClass } from 'class-transformer';
describe('TripService', () => {
let service: TripService;
let tripRepositoryMock: MockType<Repository<TripEntity>>;
let driverRepositoryMock: MockType<Repository<DriverEntity>>;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
TripService,
{ provide: getRepositoryToken(DriverEntity), useFactory: repositoryMockFactory },
{ provide: getRepositoryToken(TripEntity), useFactory: repositoryMockFactory },
],
}).compile();
service = module.get<TripService>(TripService);
driverRepositoryMock = module.get(getRepositoryToken(DriverEntity));
tripRepositoryMock = module.get(getRepositoryToken(TripEntity));
});
it('should be defined', () => {
expect(service).toBeDefined();
expect(driverRepositoryMock).toBeDefined();
expect(tripRepositoryMock).toBeDefined();
});
describe('TripService.showTrip()', () => {
const trip: TripEntity = plainToClass(TripEntity, {
id: 'one',
distance: 123,
sourceAddress: 'one',
destinationAddress: 'one',
startTime: 'one',
endTime: 'one',
createdAt: 'one',
});
it('should show the trip is it exists', async () => {
tripRepositoryMock.createQueryBuilder.mockReturnValue(trip);
await expect(service.showTrip('one', 'one')).resolves.toEqual(trip);
});
});
});
I want to mock the call to the tripRepository.createQueryBuilder().innerJoinAndSelect().where().select().getOne();
First question, should I mock the chained calls here because I assume that it should already be tested in Typeorm.
Second, if I want to mock the parameters passed to each chained call and finally also mock the return value, how can I go about it?
I had a similar need and solved using the following approach.
This is the code I was trying to test. Pay attention to the createQueryBuilder and all the nested methods I called.
const reactions = await this.reactionEntity
.createQueryBuilder(TABLE_REACTIONS)
.select('reaction')
.addSelect('COUNT(1) as count')
.groupBy('content_id, source, reaction')
.where(`content_id = :contentId AND source = :source`, {
contentId,
source,
})
.getRawMany<GetContentReactionsResult>();
return reactions;
Now, take a look at the test I wrote that simulates the chained calls of the above methods.
it('should return the reactions that match the supplied parameters', async () => {
const PARAMS = { contentId: '1', source: 'anything' };
const FILTERED_REACTIONS = REACTIONS.filter(
r => r.contentId === PARAMS.contentId && r.source === PARAMS.source,
);
// Pay attention to this part. Here I created a createQueryBuilder
// const with all methods I call in the code above. Notice that I return
// the same `createQueryBuilder` in all the properties/methods it has
// except in the last one that is the one that return the data
// I want to check.
const createQueryBuilder: any = {
select: () => createQueryBuilder,
addSelect: () => createQueryBuilder,
groupBy: () => createQueryBuilder,
where: () => createQueryBuilder,
getRawMany: () => FILTERED_REACTIONS,
};
jest
.spyOn(reactionEntity, 'createQueryBuilder')
.mockImplementation(() => createQueryBuilder);
await expect(query.getContentReactions(PARAMS)).resolves.toEqual(
FILTERED_REACTIONS,
);
});
Guilherme's answer is totally right. I just wanted to offer a modified approach that might apply to more test cases, and in TypeScript. Instead of defining your chained calls as (), you can use a jest.fn, allowing you to make more assertions. e.g.,
/* eslint-disable #typescript-eslint/no-explicit-any */
const createQueryBuilder: any = {
select: jest.fn().mockImplementation(() => {
return createQueryBuilder
}),
addSelect: jest.fn().mockImplementation(() => {
return createQueryBuilder
}),
groupBy: jest.fn().mockImplementation(() => {
return createQueryBuilder
}),
where: jest.fn().mockImplementation(() => {
return createQueryBuilder
}),
getRawMany: jest
.fn()
.mockImplementationOnce(() => {
return FILTERED_REACTIONS
})
.mockImplementationOnce(() => {
return SOMETHING_ELSE
}),
}
/* run your code */
// then you can include an assertion like this:
expect(createQueryBuilder.groupBy).toHaveBeenCalledWith(`some group`)
The solution I found to work in my case was to
create a repository class, add your custom query to the class
#EntityRepository(User)
export class UserRepository extends Repository<User> {
async getStatus(id: string) {
const status = await this.createQueryBuilder()
.select('User.id')
.where('User.id = :id', { id })
.getRawOne();
return {status};
}
}
mock the new repository class using 'jest-mock-extended' and 'jest-when' dependencies. This way you only need to mock the UserRepository and not all it's nested queries.
Now you can define the behaviour of the repository to resolve a predefined object (in my case a Partial object).
// some file where I need to call getStatus() in a test
const userRepoMock = mock<UserRepository>()
// lines omitted
const user = {
status: open,
};
when(userRepoMock.getStatus).mockResolvedValue(user as User);
// assert status