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 2+) 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
- Part 13. Testing: Frontend Testing — Integration Testing
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:
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 emule an environment similar to the Angular Testing Package.
In this post, I am going to describe the services unit test. This test are the most simple test in the test pyramid. My recommendation to the starters in the testing world is that you start unit testing the services because these are small functions which have an unique task and are easily isolated. For this reason, they are the most simple and easiest to test.
The first service we are going to test is the app.service.ts which use two services: AuthService and UserService. Therefore, our test suite must check that app.service will invoke the services using the correct parameters.
The first step consists of the initial configuration for each test that we will develop. So, the app.service.ts requires two services in its constructor (AuthService and UserService) which will be spies. 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 AppService and two spies created using a factory. The following code shows you this initial configuration:
The next step consists 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 part of the code in app.service.ts.
The authIn and authOut methods should check that the authService is invoked using the correct parameters. In our case, the test is unit and, therefore, the methods this.authService.authIn and this.authService.authOut should not be invoked using the real function/method, that is the reason why we are using spies for these methods. The code to test the functions is the following one:
In the previous tests you can note that the expect is related with the method authIn and authOut which check that these methods were invoked and the parameters were the corrects ones. In these methods errors thrown in the methods authIn or authOut are not relevant due to in these methods the responsibility is delegated to other services.
The test associated to the usersTicketing method is the following one:
In this case a spy is created to be used when the function now from Date is executed. In this case ever return the same day (the test must be pure and don't depends of external factors). Therefore, in this test we need check the method getUsersMustBeWorkingNow has been invoked and that the result of the method usersTicketing is an object which contains the key users with the value provided in the spy UserService and the timestamp of the day mocked.
The procedure to test the users service is the same that was used in app.service.ts. So, the first step is the create the test module which contains the spy and service that will be used in the following test.
The first method is very easy because the technique used is the same that in the app.service.ts. Hence, the code to test is the following one:
And its test suite only checks if the method save is invoked with the right parameters (User prototype and initial params) as you can see in the following code:
The next method to test is a call to the TypeORM ORM which you can see below:
In this test, we need spy each method from the usersRepository using chain responsability. So, the method to do this we use the factory that Jest provides.
As you see we are checking every single method from TypeORM which was called and which parameters was called with, easy and quick.
The following method could has a famous code smell (long method) but if you read carefully the method, you will notice that it is a great invocation to a database query and the code has not a code smell.
The query has several combination of parameters but the test will be the same, therefore to do this test we need a table of inputs and outputs in our test. Jest has a parameter called each which can be used to parameterize our test.
The table is the following:
You can see that the parameters used to testing in our table are the following:
year: Year corresponding to the moment in which we want to test if the user are in the building.
month: Month corresponding to the moment in which we want to test if the user are in the building.
day: Day corresponding to the moment in which we want to test if the user are in the building.
hour: Hour corresponding to the moment in which we want to test if the user are in the building.
minute: Minute corresponding to the moment in which we want to test if the user are in the building.
seconds: Seconds corresponding to the moment in which we want to test if the user are in the building.
hourNowExpected: Hour which should return the method using the other list of parameters.
dayNowExpected: Day which should return the method using the other list of parameters.
Our test need a lot of spies to testing the ORM and the expected values from the table are used to check that the privates methods return the values that will be used to the ORM query.The test will be easier if the private methods were public but a test should never change the original code (only when a bug is discovered).
The first part of the test is the creation of spies to check that it is being called using the correct parameters. Then, the method service.getUsersMustBeWorkingNow() gets invoked. Finally, there are a list of expects which check that the ORM's method are invoked using the correct parameters.
So, the final code of this test is the following:
The last service to testing is auth.service.ts. The technique to use is the similar to the previous test. So, the first step is the configuration initial in each test.
The code to test is the following one:
You can see that there are several private methods which are not possible to test directly due to the private methods are similar to copy/paste this code in the public method. Therefore, these methods have not a test suite.
The private methods are the following:
In our test suite of the methods authIn and authOut there are three different test which represent a scenario as you can see below.
should have authentication and return greetings.
should return error when the user is not found.
should return error when unexpected error appears.
should save authentication and return bye.
should return error when the user is not found.
should return error when unexpected error appears.
The authIn code is the following:
And the authOut code is the following one:
In this post I have explained how you can test services of your backend using jest and the NestJS Framework. The most interesting feature of this code, is the fact that we can use spies to isolate our tests and we can create a table of inputs and outputs to automate a lot of test that are the same but using different parameters.
In the next post, I will show you how you can unit test of the controllers.
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.
Originally published at www.carloscaballero.io on March 15, 2019.
Top comments (2)
Wow, cool to see a whole series of articles using Nest on Dev.to! I've just started using it two months ago for our in-house project and this will help me a lot for some things I'm struggling with currently.
Only problematic thing I noticed is that you don't have any validations / error handling on the service methods for adding records to the database, but I guess you did omitted that on purpose just to explain core concepts. This goes to my reading list!
This series is only to show the user query part and the near real time with Angular.
In fact, the series I do not know if to extend it developing the classic part of administration (that right now is a sad form that I did to assign quickly).
The center of this project (already in production) is to get users to enter and leave the building. The rest of validations, give much much much play for a lot.
Anything you need, you can ask, I've been working for NestJS for a while and I have several projects in production (in several different companies as a consultant).