NestJS testing with Jest custom repository (CassandraDB) - node.js

The code I am trying to test the driver / repository for my nodeJS project:
import { Injectable, OnModuleInit } from '#nestjs/common';
import { mapping, types } from 'cassandra-driver';
import { Products } from './poducts.model';
import { CassandraService } from '../database/cassandra/cassandra.service';
import Uuid = types.Uuid;
#Injectable()
export class ProductsRepository implements OnModuleInit {
constructor(private cassandraService: CassandraService) {}
productsMapper: mapping.ModelMapper<Products>;
onModuleInit() {
const mappingOptions: mapping.MappingOptions = {
models: {
Products: {
tables: ['products'],
mappings: new mapping.UnderscoreCqlToCamelCaseMappings(),
},
},
};
this.productsMapper = this.cassandraService
.createMapper(mappingOptions)
.forModel('Products');
}
async getProducts() {
return (await this.productsMapper.findAll()).toArray(); // <-----Breaks here with findAll()
}
}
I am trying to write something like this:
describe('product repository get all', () => {
it('calls the repository get all', async () => {
const await productsRepository.getProducts();
expect().DoSomething()
});
});
This is the error I am getting:
Cannot read property 'findAll' of undefined
How would I accomplish a meaning-full test with Jest to get proper code coverage?
When I try to use jest to spy on the this.products.Mapper.findAll() it seems to break every time.

Related

NestJS testing that interception has been called on a controller

I'm looking to see if there is a recommended way to write a JEST test that an Interceptor has been called. In the example below LoggingInterceptor was called? The purpose of test is verify that NestJS Binding interceptors is in place.
import { Controller, Get, UseInterceptors } from '#nestjs/common';
import { AppService } from './app.service';
import { LoggingInterceptor, TransformInterceptor } from './transform.interceptor';
#Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
#UseInterceptors(LoggingInterceptor)
#Get()
getHello(): string {
return this.appService.getHello();
}
}```
I would advise against testing that. Think about what you're testing: you're testing that the framework is doing what it says it will, something that the framework itself already tests. Sure you could use the Reflect API and verify that that metadata does exist, but that's something the framework should assert, it's a feature you should just be able to use with peace of mind.
One option I used to create a Jest test to verify that a binding interceptor(Which transformed the response) on a controller was called and produced the expected response was by using NestJS Supertest lib to simulate an end to end test.
Related NestJS doc:
List item
https://docs.nestjs.com/fundamentals/testing#end-to-end-testing
Test Code Sample:
import { INestApplication } from '#nestjs/common';
import { Test } from '#nestjs/testing';
import * as request from 'supertest';
import { CatsModule } from '../../src/cats/cats.module';
import { CatsService } from '../../src/cats/cats.service';
import { CoreModule } from '../../src/core/core.module';
describe('Test interceptor response binding was triggerred', () => {
const aServiceResponse = { findAll: () => ['test'] };
const expectedResponseAfterTransformation = { code: 200, data: [ 'test' ] };
let app: INestApplication;
beforeAll(async () => {
const moduleRef = await Test.createTestingModule({
imports: [CatsModule, CoreModule],
})
.overrideProvider(CatsService)
.useValue(aServiceResponse)
.compile();
app = moduleRef.createNestApplication();
await app.init();
});
it(`/GET cats returns transformed data respsone`, () => {
return request(app.getHttpServer()).get('/cats').expect(200).expect({
data: expectedResponseAfterTransformation,
});
});
afterAll(async () => {
await app.close();
});
});

NestJS / TypeORM: Custom repository method is not accessible in service

New to NestJS and TypeORM, and the similar questions on SO didn't solve my problem.
I have a custom TypeORM repository in NestJS using it in service, but it fails with error:
TypeError: this.tenantRepository.createTenant is not a function.
tenants.module.ts:
import { TenantRepository } from './tenant.repository';
#Module({
imports: [
TypeOrmModule.forFeature([TenantRepository]),
],
controllers: [TenantsController],
providers: [TenantsService],
})
export class TenantsModule { }
tenant.repository.ts:
// ...
import { TenantEntity } from './entities/tenant.entity';
#EntityRepository(TenantEntity)
export class TenantRepository extends Repository<TenantEntity>{
async createTenant(createTenantDto: CreateTenantDto): Promise<TenantEntity> {
const { name, email } = createTenantDto;
const newTenant = new TenantEntity()
newTenant.name = name;
newTenant.email = email;
await newTenant.save()
return newTenant;
}
}
And here's where the error is triggered (tenants.service.ts)
// ...
import { TenantEntity } from './entities/tenant.entity';
import { TenantRepository } from './tenant.repository';
#Injectable()
export class TenantsService {
constructor(
#InjectRepository(TenantRepository)
private tenantRepository: TenantRepository
) { }
async createTenant(createTenantDto: CreateTenantDto): Promise<TenantEntity> {
return await this.tenantRepository.createTenant(createTenantDto); // <-- ERROR
}
}
I can inject entity in service and use it for simple CRUD, but I want to separate concerns and use the repository pattern.
This is a POST endpoint and the error is only after submission from Swagger.
Also, VS Code autocomplete is suggesting createTenant after typing this.tenantRepository
Where am I going wrong?
EntityRepository decorator was deprecated, and as far as I know, you need to define a custom class that extends Repository and decorate it with #Injectable. Hence, you need to have some changes as follows:
tenant.repository.ts:
import { Injectable } from '#nestjs/common';
import { DataSource, Repository } from 'typeorm';
#Injectable()
export class TenantRepository extends Repository<TenantEntity>{
constructor(private dataSource: DataSource) {
super(TenantEntity, dataSource.createEntityManager());
}
async createTenant(createTenantDto: CreateTenantDto): Promise<TenantEntity> {
const { name, email } = createTenantDto;
const newTenant = this.create({ name, email });
await this.save(newTenant);
return newTenant;
}
}
tenants.module.ts:
import { TenantRepository } from './tenant.repository';
#Module({
imports: [
TypeOrmModule.forFeature([TenantRepository]),
],
controllers: [TenantsController],
providers: [TenantsService, TenantRepository],
})
export class TenantsModule { }
tenants.service.ts:
import { TenantEntity } from './entities/tenant.entity';
import { TenantRepository } from './tenant.repository';
#Injectable()
export class TenantsService {
constructor(
private tenantRepository: TenantRepository
) { }
async createTenant(createTenantDto: CreateTenantDto): Promise<TenantEntity> {
return await this.tenantRepository.createTenant(createTenantDto);
}
}
You also have access to built-in typeorm methods like save, create, find, etc. since the custom repository is derived from Repository class.

How To Mock Repository, Service and Controller In NestJS (Typeorm & Jest)

I'm new at typescript. My Nestjs project app is something like this. I'm trying to use repository pattern, so i separated business logic (service) and persistance logic (repository)
UserRepository
import { Injectable } from '#nestjs/common';
import { InjectRepository } from '#nestjs/typeorm';
import { Repository } from 'typeorm';
import { UserEntity } from './entities/user.entity';
#Injectable()
export class UserRepo {
constructor(#InjectRepository(UserEntity) private readonly repo: Repository<UserEntity>) {}
public find(): Promise<UserEntity[]> {
return this.repo.find();
}
}
UserService
import { Injectable } from '#nestjs/common';
import { UserRepo } from './user.repository';
#Injectable()
export class UserService {
constructor(private readonly userRepo: UserRepo) {}
public async get() {
return this.userRepo.find();
}
}
UserController
import { Controller, Get } from '#nestjs/common';
import { UserService } from './user.service';
#Controller('/users')
export class UserController {
constructor(private readonly userService: UserService) {}
// others method //
#Get()
public async getUsers() {
try {
const payload = this.userService.get();
return this.Ok(payload);
} catch (err) {
return this.InternalServerError(err);
}
}
}
How do i create unit testing for repository, service & controller without actually persist or retrieve data to DB (using mock)?
Mocking in NestJS is pretty easily obtainable using the testing tools Nest exposes is #nestjs/testing. In short, you'll want to create a Custom Provider for the dependency you are looking to mock, and that's all there is. However, it's always better to see an example, so here is a possibility of a mock for the controller:
describe('UserController', () => {
let controller: UserController;
let service: UserService;
beforeEach(async () => {
const moduleRef = await Test.createTestingModule({
controllers: [UserController],
providers: [
{
provide: UserService,
useValue: {
get: jest.fn(() => mockUserEntity) // really it can be anything, but the closer to your actual logic the better
}
}
]
}).compile();
controller = moduleRef.get(UserController);
service = moduleRef.get(UserService);
});
});
And from there you can go on and write your tests. This is pretty much the same set up for all tests using Nest's DI system, the only thing to be aware of is things like #InjectRepository() and #InjectModel() (Mongoose and Sequilize decorators) where you'll need to use getRepositoryToken() or getModelToken() for the injection token. If you're looking for more exmaples take a look at this repository

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:

How to jest.spyOn only the base class method, not the overridden method

Trying to write test scripts for my nestjs application.
I have controller/service framework, that looks like this:
Controller:
export class MyController {
constructor(
protected _svc: MyService
) {}
#Get()
async getAll(): Promise<Array<Person>> {
return await this._svc.findAll();
}
}
Service:
#Injectable()
export class MyService extends DbService < Person > {
constructor(
private _cache: CacheService
) {
super(...);
}
async findAll() {
return super.findAll().then(res => {
res.map(s => {
this._cache.setValue(`key${s.ref}`, s);
});
return res;
});
}
Base class:
#Injectable()
export abstract class DbService<T> {
constructor() {}
async findAll(): Promise<Array<T>> {
...
}
}
My controller is the entry point when calling an endpoint on the API. This calls the service, which extends the DbService, which is what communicates with my database. There are a lot of services which all extend this DbService. In this case, the MyService class overrides the DbService "findAll" method to do some cache manipulation.
My test script has this:
let myController: MyController;
let myService: MyService;
describe("MyController", async () => {
let spy_findall, spy_cacheset;
beforeAll(() => {
this._cacheService = {
// getValue, setValue, delete methods
};
myService = new MyService(this._cacheService);
myController = new MyController(myService);
spy_findall = jest.spyOn(myService, "findAll").mockImplementation(async () => {
return [testPerson];
});
spy_cacheset = jest.spyOn(this._cacheService, "setValue");
});
beforeEach(async () => {
jest.clearAllMocks();
});
describe("getAll", () => {
it("should return an array of one person", async () => {
await myController.getAll().then(r => {
expect(r).toHaveLength(1);
expect(spy_findall).toBeCalledTimes(1);
expect(spy_cacheset).toBeCalledTimes(1);
expect(r).toEqual([testPerson]);
});
});
});
});
Now, obviously the mockImplementation of findAll mocks the "findAll" on MyService, so the test fails because spy_cacheset is never called.
What I would like to do is mock only the base method "findAll" from DbService, so that I maintain the extra functionality that exists in MyService.
Is there a way of doing this without just renaming the methods in MyService, which I would rather avoid doing?
Edited to add:
Thanks to #Jonatan lenco for such a comprehensive reponse, which I have taken on board and implemented.
I have one further question. CacheService, DbService and a whole lot of other stuff (some of which I want to mock, other that I don't) is in an external library project, "shared".
cache.service.ts
export class CacheService {...}
index.ts
export * from "./shared/cache.service"
export * from "./shared/db.service"
export * from "./shared/other.stuff"
....
This is then compiled and included as a package in node_modules.
In the project where I am writing the tests:
import { CacheService, DocumentService, OtherStuff } from "shared";
Can I still use jest.mock() for just the CacheService, without mocking the whole "shared" project?
In this case since you want to spy on an abstract class (DbService), you can spy on the prototype method:
jest.spyOn(DbService.prototype, 'findAll').mockImplementation(async () => {
return [testPerson];
});
Also here some recommendations for your unit tests with NestJS and Jest:
Use jest.mock() in order to simplify your mocking (in this case for CacheService). See https://jestjs.io/docs/en/es6-class-mocks#automatic-mock.
When you do jest.spyOn(), you can assert the method execution without the need of the spy object. Instead of:
spy_findall = jest.spyOn(myService, "findAll").mockImplementation(async () => {
return [testPerson];
});
...
expect(spy_findall).toBeCalledTimes(1);
You can do:
jest.spyOn(DbService.prototype, 'findAll').mockImplementation(async () => {
return [testPerson];
});
...
expect(DbService.prototype.findAll).toBeCalledTimes(1);
If you are mocking a class properly, you do not need to spy on the method (if you do not want to mock its implementation).
Use the Testing utilities from NestJS, it will help you a lot specially when you have complex dependency injection. See https://docs.nestjs.com/fundamentals/testing#testing-utilities.
Here is an example that applies these 4 recommendations for your unit test:
import { Test } from '#nestjs/testing';
import { CacheService } from './cache.service';
import { DbService } from './db.service';
import { MyController } from './my.controller';
import { MyService } from './my.service';
import { Person } from './person';
jest.mock('./cache.service');
describe('MyController', async () => {
let myController: MyController;
let myService: MyService;
let cacheService: CacheService;
const testPerson = new Person();
beforeAll(async () => {
const module = await Test.createTestingModule({
controllers: [MyController],
providers: [
MyService,
CacheService,
],
}).compile();
myService = module.get<MyService>(MyService);
cacheService = module.get<CacheService>(CacheService);
myController = module.get<MyController>(MyController);
jest.spyOn(DbService.prototype, 'findAll').mockImplementation(async () => {
return [testPerson];
});
});
beforeEach(async () => {
jest.clearAllMocks();
});
describe('getAll', () => {
it('Should return an array of one person', async () => {
const r = await myController.getAll();
expect(r).toHaveLength(1);
expect(DbService.prototype.findAll).toBeCalledTimes(1);
expect(cacheService.setValue).toBeCalledTimes(1);
expect(r).toEqual([testPerson]);
});
});
});
NOTE: for the testing utilities to work and also for your application to work well, you will need to add the #Controller decorator on the class MyController:
import { Controller, Get } from '#nestjs/common';
...
#Controller()
export class MyController {
...
}
About mocking specific items of another package (instead of mocking the whole package) you could do this:
Create a class in your spec file (or you can create it in another file that you import, or even in your shared module) which has a different name but has the same public method names. Note that we use jest.fn() since we do not need to provide an implementation, and that already spies in the method (no need to later do jest.spyOn() unless you have to mock the implementation).
class CacheServiceMock {
setValue = jest.fn();
}
When setting up the providers of your testing module, tell it that you are "providing" the original class but actually providing the mocked one:
const module = await Test.createTestingModule({
controllers: [MyController],
providers: [
MyService,
{ provide: CacheService, useClass: CacheServiceMock },
],
}).compile();
For more info about providers see https://angular.io/guide/dependency-injection-providers (Nest follows the same idea of Angular).

Resources