Part 10. Testing: Backend Testing - Unit Testing - Controllers
This post is part of a Series of post which I'm describing a clock-in/out system if you want to read more you can read the following posts:
- Part 1. Clock-in/out System: Diagram.
- Part 2. Clock-in/out System: Basic backend - AuthModule.
- Part 3. Clock-in/out System: Basic backend - UsersModule.
- Part 4. Clock-in/out System: Basic backend- AppModule.
- Part 5. Clock-in/out System: Seed Database and migration data
- Part 6. Clock-in/out System: Basic frontend.
- Part 7. Clock-in/out System: Deploy backend (nestJS) using docker/docker-compose
- Part 8. Clock-in/out System: Deploy frontend (Angular 6+) using environments
- Part 9. Testing: Backend Testing - Unit Testing - Services
- Part 10. Testing: Backend Testing - Unit Testing - Controllers
- Part 11. Testing: Backend Testing - E2E Testing
- Part 12. Testing: Frontend Testing - Unit Testing - Services
- Part 13. Testing: Frontend Testing - Unit Testing - Controllers
Introduction
This is the first post about testing and can be the first post about Quality Assessment (QA). This project has not been developed using Test Drive-Development (TDD) from the beginning but I am currently doing the testing phase. Thanks to the testing phase I have identified a lot of mini-bugs which could had been a big problem in case this project had been in production. The reality is that the project will be in production mode in the following weeks. These tests will be very useful to repair several bugs that have been discovered in this time.
The first step to test is deciding what should I test? Anybody could say to you that you must testing the whole app and you must obtain a coverage near to 100% but the really is that you do not need to test the entire app but that you have to test the most critical parts of your software. These parts of your software could be a value near to 90% or 70% depending on your app.
In our case, I am going to describe that we should test:
- Services:
- app.service.
- user.service.
- auth.service.
- Controllers:
- app.controller.
- user.controller.
Therefore, in our project, there is no need to do test DTOs, constants, entities and modules because those test are hard and the value is small.
The backend is developed using the NestJS framework which uses Jest as testing tool. Furthermore, NestJS includes a powerful package to testing which emulate an environment similar to the Angular Testing Package.
Controllers Testing
In this post, I'm going to describe the controllers unit test. This test are the most simple test in the test pyramid after of services unit test. My recommendation to the starters in the testing world is that you start unit test of the services because these are small functions which have an unique task and are easily isolated. Then, the next step in the testing world is that you doing controllers unit test because the controllers frequently invoke services methods.
App Controller
The first controller we are going to test is the app.controller.ts
which use a service: AppService
. Therefore, our test suite must check that app.service
will invoke the methods using the correct parameters.
The first step consists of the initial configuration for each test that we will develop. So, the app.controller.ts
requires a service in its constructor (AppService
) which will be a spy. The Test
package from @nestjs/testing
provides the method createTestingModule
which creates a testing module to test. In this testingModule
the providers
array is composed by the AppController
and a spy created using a factory to intercept the AppService
. The following code shows you this initial configuration:
describe('App Controller', () => {
let testingModule: TestingModule;
let controller: AppController;
let spyService: AppService;
beforeEach(async () => {
testingModule = await Test.createTestingModule({
controllers: [AppController],
providers: [
{
provide: AppService,
useFactory: () => ({
authIn: jest.fn(() => true),
authOut: jest.fn(() => true),
usersTicketing: jest.fn(() => true),
}),
},
],
}).compile();
controller = testingModule.get(AppController);
spyService = testingModule.get(AppService);
});
...
The next step consits of knowing what we want to test. The main idea is to test each function/method independently of any other. So, the following methods are the code of app.controller.ts
.
@Post('in')
authIn(@Body() ticket: AuthDto): Promise<AuthResponseDto> {
return this.appService.authIn(ticket);
}
@Post('out')
authOut(@Body() ticket: AuthDto): Promise<AuthResponseDto> {
return this.appService.authOut(ticket);
}
@Get('users')
usersTicketing(): Promise<{ users: User[]; timestamp: number }> {
return this.appService.usersTicketing();
}
The authIn
, authOut
and ``ùsersTicketingmethods should check that the
appService``` is invoke using the correct parameters. In our case, the test is unit and, therefore, these methods should not be invoke using the real function/method, that is the reason why we are using a spy for these methods. The code to test the functions is the following one:
describe('authIn', () => {
it('should authIn an user', async () => {
const params: AuthDto = {
key: 'key',
reader: 'reader',
};
controller.authIn(params);
expect(spyService.authIn).toHaveBeenCalledWith(params);
});
});
describe('authOut', () => {
it('should authOut an user', async () => {
const params: AuthDto = {
key: 'key',
reader: 'reader',
};
controller.authOut(params);
expect(spyService.authOut).toHaveBeenCalledWith(params);
});
});
describe('usersTicketing', () => {
it("should return the user's list who clock-in", async () => {
controller.usersTicketing();
expect(spyService.usersTicketing).toHaveBeenCalled();
});
});
In the previous tests you can note that the expect
is related with the method authIn
, authOut
and usersTicketing
which check that these methods were invoke and the parameters were the corrects ones. In these methods the errors thrown in the methods authIn
or authOut
are not relevant due to in theses methods the responsibility is delegated to the AppService
.
User Controller
The procedure to test the user controller is the same that was used in app.controller.ts
. So, the first step is the create the test module which contains the spy and controller that will be used in the following test.
import { Test } from '@nestjs/testing';
import { TestingModule } from '@nestjs/testing/testing-module';
import { UserService } from '../services/users.service';
import { UserController } from './user.controller';
describe('User Controller', () => {
let testingModule: TestingModule;
let controller: UserController;
let spyService: UserService;
beforeEach(async () => {
testingModule = await Test.createTestingModule({
controllers: [UserController],
providers: [
{
provide: UserService,
useFactory: () => ({
getUsersWithoutKey: jest.fn(() => true),
addUser: jest.fn(() => true),
}),
},
],
}).compile();
controller = testingModule.get(UserController);
spyService = testingModule.get(UserService);
});
...
The methods are very easy to testing because the technique used is the same that in the app.controller.ts
. So, the code to test is the following:
@Get()
getUsers(): Promise<User[]> {
return this.userService.getUsersWithoutKey();
}
@Post()
addUser(@Body() userDto: { uid: string; key: string }): Promise<User> {
return this.userService.addUser(userDto);
}
And its test suite only check if the methods getUsersWithoutKey
and addUser
are invoked with the correct parameters as you can see in the following code:
describe('getUser', () => {
it('should return users withoutKey', async () => {
controller.getUsers();
expect(spyService.getUsersWithoutKey).toHaveBeenCalled();
});
});
describe('addUser', () => {
it('should add key to the user', async () => {
const params = {
uid: 'uid',
key: 'string',
}
controller.addUser(params);
expect(spyService.addUser).toHaveBeenCalledWith(params);
});
});
Conclusion
In this post I have explained how you can test controllers of your backend using jest and the NestJS Framework. These test are very easy if you know as the spies works. In fact, the controllers frequently invoke methods of the services.
In the next post, I will show you as you can to do e2e test of the backend.
- The GitHub project is https://github.com/Caballerog/clock-in-out.
- The GitHub branch of this post is https://github.com/Caballerog/clock-in-out/tree/part9-backend-unit-test.