Mock Injectable model in nestjs with Jest - javascript

I want to unit test my service. Inside my service I have a constructor that is:
contractService.ts:
export class ContractService {
private logger = new Logger("ContractService");
constructor(
#InjectModel(Contract)
private contractModel: typeof Contract
) {}
async getContracts(query: PaginationInterface): Promise<FetchContract> {
const { limit, page, skip } = paginationParseParams(query);
const { sortBy, direction } = sortParseParams(query, ColumnDetails);
const { count, rows } = await this.contractModel.findAndCountAll({
where: {},
offset: skip,
limit: limit,
order: [[sortBy, direction]],
});
const pages = Math.ceil(count / limit);
const meta = {
limit,
skip,
page,
count,
pages,
sortBy,
direction,
};
return { meta, data: rows };
}
}
My model looks like this: (Model is a class from sequelize-typescript)
export class Contract extends Model<Contract> {
....
}
So I want to create my unit test with jest. When I try to mock the contractModel, it does not find the method, eventhough I am trying to mock it.
const mockContractModel = () => ({
findAndCountAll: jest.fn(),
});
describe("ContractService", () => {
let contractService: ContractService;
let contractModel: Contract;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
ContractService,
{
provide: Contract,
useFactory: mockContractModel,
},
],
}).compile();
contractService = module.get<ContractService>(ContractService);
contractModel = module.get<Contract>(Contract);
});
it("should be defined", () => {
expect(contractService).toBeDefined();
});
describe("Get contracts", () => {
it("Should return all the contracts", async () => {
expect(contractModel.findAndCountAll).not.toHaveBeenCalled();
await contractService.getContracts(defaultPagination);
expect(contractModel.findAndCountAll).toHaveBeenCalled();
});
});
});
What is the right way to mock this contractModel?

Instead of using
{
provide: Contract,
useFactory: mockContractModel
}
You should be using
{
provide: getModelToken(Contract),
useFactory: mockContractModel
}
where getModelToken is imported from #nestjs/mongoose. This will get the correct DI token for Nest to know what you're mocking. For more examples, check this git repo

Related

How to create a test for the below controller?

do I need to write a test for the below code?
#Post()
update(#Body() updateUserDto: UpdateUserDto, #Request() req) {
return this.userService.update(req.user.user, updateUserDto);
}
I'm going to make some assumptions about injected properties of the controller class. For full setups, you can view my testing example repo here. This should be essentially what you're looking for though
describe('UserController (Unit)', () => {
let controller: UserController;
let service: MockedClass<UserService>;
beforeAll(async () => {
const modRef = await Test.createTestingModule({
controllers: [UserController],
providers: [
{
provide: UserService,
useValue: {
update: jest.fn().mockReturnValue({ hello: 'world' }),
},
}
],
}).compile();
controller = modRef.get(UserController);
service = modRef.get(UserService);
});
it('should call update and return the value', () => {
const bodyVal = objectThatMatchesUpdateUserDto;
const reqVal = {
user: {
user: valueThatMatchesReqUserUser
}
};
const res = controller.update(bodyVal, reqVal);
expect(res).toEqual({ hello: 'world' });
expect(service.update).toBeCalledWith(reqVal.user.user, bodyVal)
});
});

Am I supposed to pass around Pino child loggers?

This is rather a stylistic question. I'm using Pino in some of my Javascript/Typescript microservices. As they're running on AWS I'd like to propagate the RequestId.
When one of my functions is invoked, I'm creating a new child logger like this:
const parentLogger = pino(pinoDefaultConfig)
function createLogger(context) {
return parentLogger.child({
...context,
})
}
function createLoggerForAwsLambda(awsContext) {
const context = {
requestId: awsContext.awsRequestId,
}
return createLogger(context)
}
I'm then passing down the logger instance to all methods. That said, (... , logger) is in almost every method signature which is not too nice. Moreover, I need to provide a logger in my tests.
How do you do it? Is there a better way?
you should implement some sort of Dependency Injection and include your logger there.
if your using microservices and maybe write lambdas in a functional approach, you can handle it by separating the initialization responsibility in a fashion like this:
import { SomeAwsEvent } from 'aws-lambda';
import pino from 'pino';
const createLogger = (event: SomeAwsEvent) => {
return pino().child({
requestId: event.requestContext.requestId
})
}
const SomeUtil = (logger: pinno.Logger) => () => {
logger.info('SomeUtil: said "hi"');
}
const init(event: SomeAwsEvent) => {
const logger = createLogger(event);
someUtil = SomeUtil(logger);
return {
logger,
someUtil
}
}
export const handler = (event: SomeAwsEvent) => {
const { someUtil } = init(event);
someUtil();
...
}
The simplest way is to use some DI library helper to tackle this
import { createContainer } from "iti"
interface Logger {
info: (msg: string) => void
}
class ConsoleLogger implements Logger {
info(msg: string): void {
console.log("[Console]:", msg)
}
}
class PinoLogger implements Logger {
info(msg: string): void {
console.log("[Pino]:", msg)
}
}
interface UserData {
name: string
}
class AuthService {
async getUserData(): Promise<UserData> {
return { name: "Big Lebowski" }
}
}
class User {
constructor(private data: UserData) {}
name = () => this.data.name
}
class PaymentService {
constructor(private readonly logger: Logger, private readonly user: User) {}
sendMoney() {
this.logger.info(`Sending monery to the: ${this.user.name()} `)
return true
}
}
export async function runMyApp() {
const root = createContainer()
.add({
logger: () =>
process.env.NODE_ENV === "production"
? new PinoLogger()
: new ConsoleLogger(),
})
.add({ auth: new AuthService() })
.add((ctx) => ({
user: async () => new User(await ctx.auth.getUserData()),
}))
.add((ctx) => ({
paymentService: async () =>
new PaymentService(ctx.logger, await ctx.user),
}))
const ps = await root.items.paymentService
ps.sendMoney()
}
console.log(" ---- My App START \n\n")
runMyApp().then(() => {
console.log("\n\n ---- My App END")
})
it is easy to write tests too:
import { instance, mock, reset, resetCalls, verify, when } from "ts-mockito"
import { PaymentService } from "./payment-service"
import type { Logger } from "./logger"
const mockedLogger = mock<Logger>()
when(mockedLogger.info).thenReturn(() => null)
describe("Payment service: ", () => {
beforeEach(() => {
resetCalls(mockedLogger)
// reset(mockedLogger)
})
it("should call logger info when sending money", () => {
const paymentService = new PaymentService(instance(mockedLogger))
expect(paymentService.sendMoney()).toBe(true)
})
})
I would not use the requestId as part of the context of the logger, but use it as the payload of the logger, like logger.info({ requestId }, myLogMessage). This was you can have a simple function create a child logger that you can use for the entire module.

Test NestJS Service against Actual Database

I would like to be able to test my Nest service against an actual database. I understand that most unit tests should use a mock object, but it also, at times, makes sense to test against the database itself.
I have searched through SO and the GH issues for Nest, and am starting to reach the transitive closure of all answers. :-)
I am trying to work from https://github.com/nestjs/nest/issues/363#issuecomment-360105413. Following is my Unit test, which uses a custom provider to pass the repository to my service class.
describe("DepartmentService", () => {
const token = getRepositoryToken(Department);
let service: DepartmentService;
let repo: Repository<Department>;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
DepartmentService,
{
provide: token,
useClass: Repository
}
]
}).compile();
service = module.get<DepartmentService>(DepartmentService);
repo = module.get(token);
});
Everything compiles properly, TypeScript seems happy. However, when I try to execute create or save on my Repository instance, the underlying Repository appears to be undefined. Here's the stack backtrace:
TypeError: Cannot read property 'create' of undefined
at Repository.Object.<anonymous>.Repository.create (repository/Repository.ts:99:29)
at DepartmentService.<anonymous> (relational/department/department.service.ts:46:53)
at relational/department/department.service.ts:19:71
at Object.<anonymous>.__awaiter (relational/department/department.service.ts:15:12)
at DepartmentService.addDepartment (relational/department/department.service.ts:56:16)
at Object.<anonymous> (relational/department/test/department.service.spec.ts:46:35)
at relational/department/test/department.service.spec.ts:7:71
It appears that the EntityManager instance with the TypeORM Repository class is not being initialized; it is the undefined reference that this backtrace is complaining about.
How do I get the Repository and EntityManager to initialize properly?
thanks,
tom.
To initialize typeorm properly, you should just be able to import the TypeOrmModule in your test:
Test.createTestingModule({
imports: [
TypeOrmModule.forRoot({
type: 'mysql',
// ...
}),
TypeOrmModule.forFeature([Department])
]
I prefer not using #nestjs/testing for the sake of simplicity.
First of all, create a reusable helper:
/* src/utils/testing-helpers/createMemDB.js */
import { createConnection, EntitySchema } from 'typeorm'
type Entity = Function | string | EntitySchema<any>
export async function createMemDB(entities: Entity[]) {
return createConnection({
// name, // let TypeORM manage the connections
type: 'sqlite',
database: ':memory:',
entities,
dropSchema: true,
synchronize: true,
logging: false
})
}
Then, write test:
/* src/user/user.service.spec.ts */
import { Connection, Repository } from 'typeorm'
import { createMemDB } from '../utils/testing-helpers/createMemDB'
import UserService from './user.service'
import User from './user.entity'
describe('User Service', () => {
let db: Connection
let userService: UserService
let userRepository: Repository<User>
beforeAll(async () => {
db = await createMemDB([User])
userRepository = await db.getRepository(User)
userService = new UserService(userRepository) // <--- manually inject
})
afterAll(() => db.close())
it('should create a new user', async () => {
const username = 'HelloWorld'
const password = 'password'
const newUser = await userService.createUser({ username, password })
expect(newUser.id).toBeDefined()
const newUserInDB = await userRepository.findOne(newUser.id)
expect(newUserInDB.username).toBe(username)
})
})
Refer to https://github.com/typeorm/typeorm/issues/1267#issuecomment-483775861
Here's an update to the test that employs Kim Kern's suggestion.
describe("DepartmentService", () => {
let service: DepartmentService;
let repo: Repository<Department>;
let module: TestingModule;
beforeAll(async () => {
module = await Test.createTestingModule({
imports: [
TypeOrmModule.forRoot(),
TypeOrmModule.forFeature([Department])
],
providers: [DepartmentService]
}).compile();
service = module.get<DepartmentService>(DepartmentService);
repo = module.get<Repository<Department>>(getRepositoryToken(Department));
});
afterAll(async () => {
module.close();
});
it("should be defined", () => {
expect(service).toBeDefined();
});
// ...
}
I created a test orm configuration
// ../test/db.ts
import { TypeOrmModuleOptions } from '#nestjs/typeorm';
import { EntitySchema } from 'typeorm';
type Entity = Function | string | EntitySchema<any>;
export const createTestConfiguration = (
entities: Entity[],
): TypeOrmModuleOptions => ({
type: 'sqlite',
database: ':memory:',
entities,
dropSchema: true,
synchronize: true,
logging: false,
});
which I then utilize when setting up the tests
// books.service.test.ts
import { Test, TestingModule } from '#nestjs/testing';
import { HttpModule, HttpService } from '#nestjs/common';
import { TypeOrmModule, getRepositoryToken } from '#nestjs/typeorm';
import { Repository } from 'typeorm';
import { BooksService } from './books.service';
import { Book } from './book.entity';
import { createTestConfiguration } from '../../test/db';
describe('BooksService', () => {
let module: TestingModule;
let service: BooksService;
let httpService: HttpService;
let repository: Repository<Book>;
beforeAll(async () => {
module = await Test.createTestingModule({
imports: [
HttpModule,
TypeOrmModule.forRoot(createTestConfiguration([Book])),
TypeOrmModule.forFeature([Book]),
],
providers: [BooksService],
}).compile();
httpService = module.get<HttpService>(HttpService);
service = module.get<BooksService>(BooksService);
repository = module.get<Repository<Book>>(getRepositoryToken(Book));
});
afterAll(() => {
module.close();
});
it('should be defined', () => {
expect(service).toBeDefined();
});
This allows to query the repository after the tests and ensure that the correct data was inserted.
I usually import AppModule for database connection, and finally after tests are executed I close the connection:
let service: SampleService;
let connection: Connection;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [AppModule, TypeOrmModule.forFeature([SampleEntity])],
providers: [SampleService],
}).compile();
service = module.get<SampleService>(SampleService);
connection = await module.get(getConnectionToken());
});
afterEach(async () => {
await connection.close();
});
I used existing AppModule for testing:
import { INestApplication } from '#nestjs/common';
import { NestFactory } from '#nestjs/core';
import { AppModule } from '../../../app.module';
import { AuthService } from '../auth.service';
describe('AuthService', () => {
let app: INestApplication;
let service: AuthService;
beforeAll(async () => {
app = await NestFactory.create(AppModule);
service = app.get(AuthService);
});
afterAll(async () => {
await app.close();
});
it('AuthService should be defined', () => {
expect(service).toBeDefined();
});
describe('login', () => {
it('should login user', async () => {
const user = await service.login({ email: 'xxxzei#mail.ru', password: '12345678' });
expect(user.id).toBeDefined();
});
});
});
Also to add env variables in your configuration file or AppModule:
import { config as testConfig } from 'dotenv';
if (process.env.NODE_ENV === 'test') {
testConfig({ path: resolve('./.env') });
}

Testing in Angular with service dependency

I'm trying to write my first Angular 6 test. I have a component which returns a list of Companies from a service.
It looks like this:
Template
<div *ngFor="let company of this.companies">
<h4 id="company-{{company.id}}>{{company.name}}</h4>
</div>
Component.ts
import { ApiService } from '../service/api.service';
ngOnInit(): void {
this.companies = this.apiService.getCompanies();
}
Service
import { COMPANYLIST } from '../companyList';
companyList = COMPANYLIST;
public getCompanies(): Company[] {
return this.companyList;
}
I would like to test that I can see the list of Companies in the component. In my spec.ts I have tried to add a mocked apiService as per https://angular.io/guide/testing#component-with-a-dependency with no luck.
I'm guessing the test should look something like this, but I am having issues actually injecting the mocked service into this test.
it("should show the list of Companies", () => {
const compiled = fixture.debugElement.nativeElement;
expect(compiled.querySelector("company-" + this.company.id).textContent).toContain("Mock Company");
});
The strategy is to inject a place holder object for your service. In the test get a reference to that place holder object and then add fake functionality to it that will be called when testing the component.
Example (I omitted code that does not illustrate the point I am trying to make)
import { ApiService } from '../service/api.service';
describe('CompaniesComponent Tests', () => {
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [CompaniesComponent],
providers: [
{ provide: ApiService, useValue: {} }
]
}).compileComponents();
TestBed.compileComponents();
fixture = TestBed.createComponent(CompaniesComponent);
comp = fixture.componentInstance;
});
it("should show the list of Companies", () => {
// get service and fake a returned company list
const apiService = TestBed.get(ApiService) as ApiService;
apiService.getCompanies = () => ['Mock Company'];
// you probably need to call ngOnInit on your component so it retrieves the companies from the api service
comp.ngOnInit();
const compiled = fixture.debugElement.nativeElement;
expect(compiled.querySelector("company-" + this.company.id).textContent).toContain("Mock Company");
});
You can mock your service :
export class MockCompanyService {
getCompanies: Spy;
constructor(config) {
this.getCompanies = jasmine.createSpy('getCompanies').and.callFake(() => config.companies);
}
}
In your test, you will give companies to you mock so when your function is called, you will have your companies displayed.:
describe('CompanyComponent', () => {
let component: CompanyComponent;
let fixture: ComponentFixture<CompanyComponent>;
let element;
const mockCompanies = [
...
];
beforeEach(async(() => {
return TestBed
.configureTestingModule({
declarations: [
CompanyComponent
],
imports: [],
providers: [
{
provide: ComponentFixtureAutoDetect,
useValue: true
}
]
})
.overrideComponent(CompanyComponent, {
set: {
providers: [
{provide: CompanyService, useValue: new MockCompanyService(mockCompanies)}
]
}
})
.compileComponents()
.then(() => {
fixture = TestBed.createComponent(CompanyComponent);
component = fixture.debugElement.componentInstance;
element = fixture.debugElement.nativeElement;
});
}));
it('should create', () => {
expect(component).toBeTruthy();
});
it('...', () => {
});
});
in your example the unit test should be pretty simple to implement.
It should be something like that:
describe("path", () => {
let component: Component;
let fixture: ComponentFixture<Component>
let service: Service;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [Component],
providers: [Service]
});
fixture = TestBet.CreateComponent(Component)
service = TestBed.get(Service)
});
afterEach(() => {
fixture.destroy();
});
it("Component_Method_WhatDoYouExpect", () => {
let testCompanies = [{c1}....];
let spy = spyOn(service, "getCompanies").and.returnValue(testCompanies);
component.ngOnInit();
expect(spy).toHaveBeenCalled();
expect(component.companies).toEqual(testCompanies);
});
});
You have to create a test file for the component and one for the service.
In service test you should do almost the same like above, but there you have to initialize the company list, to call the method and to verify if the result is right.
service.companyList = [c1, c2...]
let res = service.GetCompanies();
expect(res).toEqual(service.companyList);
Here you can find more information about TestBed and Unit tests.

Expected undefined to equal 2

Technologies Used: Karma/Jasmine, Angular2
service.ts
Injectable()
export class Service {
constructor(private http: Http) { }
getGoogle(): Observable<any>{
console.log("Inside service");
return this.http.get('https://jsonplaceholder.typicode.com/posts/1');
}}
Please ignore the Typos and all the imports are made correctly.API is getting called correctly in the UI.
service.spec.ts
describe('Provider:Service', () => {
const HERO_ONE = 'HeroNrOne';
const HERO_TWO = 'WillBeAlwaysTheSecond';
let lastConnection;
let service;
beforeEach(() => {
let injector = ReflectiveInjector.resolveAndCreate([
{ provide: ConnectionBackend, useClass: MockBackend },
{ provide: RequestOptions, useClass: BaseRequestOptions },
Http,
Service
]);
service = injector.get(Service);
let backend = injector.get(ConnectionBackend) as MockBackend;
backend.connections.subscribe((connection: any) => lastConnection = connection);
});
it('getGoogle() should return the data in json format', fakeAsync(() => {
console.log('1');
let result:String[];
service.getGoogle().toPromise().then((heroes:String[]) => result = heroes);
lastConnection.mockRespond(new Response(new ResponseOptions({
body:JSON.stringify({data: [HERO_ONE, HERO_TWO]}),
})));
tick();
console.log(result[0]);
console.log('3');
expect(result.length).toEqual(2, 'should contain given amount of
heroes'); //this spec is failing because results does is not getting the response.
}));
The result:String[] is not getting the response that is provided.

Categories

Resources