d

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore.

15 St Margarets, NY 10033
(+381) 11 123 4567
ouroffice@aware.com

 

KMF

TDD Typescript NestJS API Layers with Jest Part 2: Service Unit Test

Context

This is a 3 part series for unit testing the controller, service, and repository layers in a typical REST architecture. There are many approaches to doing this, I’m sharing a pattern that I find very flexible and hopefully, you can pick up some tips and tricks along the way.

Part 1 — Unit Testing the Controller | Git branch can be found here.

Part 2 — Unit Testing the Service | Git branch can be found here.

Part 3 — Unit Testing the Repository | Git branch can be found here.

Intro

Following on from part 1 of testing the controller, the minimum was done to move on to the service. As before, you can follow along.

Set the Scene

Story: ‘The information about the space ship needs to be saved.’

We now have parsed and valid data in the SpaceShip object DTO. We want to transform this into a DB entity and pass it to the repo.

Let’s go!

Unit Testing the Service

The service is responsible for carrying out any business logic and passing the data to the repository. We can now add the same thought process as above:

Given — a space ship object;
When — saving;
Then — it should handle the success scenario;
And — it should handle the fail scenario.

So let’s start by creating the repo, add the save function, and create the mock in the test. This should yield our failing test:

nest g provider space-ship-repository

Now the test should fail because it’s throwing an Error("Method not implemented."),  let’s inject the repo into the service and call the function:

Now the test is passing. This is ok but is wrong. The SpaceShip DTO needs to be converted to an entity. TypeOrm will be used as it provides lots of shortcuts to get things done quickly and easily. We’ll now create an entity with a few extra fields after installing TypeOrm:

npm install --save @nestjs/typeorm typeorm

This is a sample entity, it has the fields from the SpaceShip object plus a date created field — it’s very common for the entity to have other fields than what we are trying to save. Notice the annotations, these are pretty self-documenting. 

Update the repository to take a SpaceShipEntity instead of the any type. Also, update the test to expect a the same:

Now the test should fail because it’s not receiving the entity. Now we need to convert the DTO to an entity for the repository.

As before, a class will be injected to transform the object:

nest g provider space-ship-to-entity

We can do many more tests here and throw errors if not valid, etc., so we’re not going to repeat this here. It’s likely that there would be other parsing required, eg., date transformation or data enhancement from other systems, etc. So while it looks pointless now, as the codebase grows this becomes more valuable. But now we have also taken this logic out of the service so it can focus on handling the save.

The service test can now be updated to check that the converter and repo save are called with the correct data. As we are not really testing what is being passed into the convert (has its own tests for that) we can just ‘fudge the data’ and just checked its called with the SpaceShip and the same can be done for the save.

We are not concerned with what is being saved, just the fact that it is called with the data it is given.

Problem: We Have Not Accounted for the Return Type

TDD is difficult and can require a lot of back and forth changing and updating things. 

It would be easy to re-write this article and know exactly what to do from the start. This option isn’t available in the real-world and is much harder to anticipate steps further down the line. However, this process becomes easier with more practice and experience.

So back to the problem, the return type has been overlooked and was not known at the time of writing. The repo save function will return a promise based on the result of saving. So let’s update the test to return a promise from the repo, the repo will also return an entity that will need to be converted to a DTO. With these steps, we can write the test to check for the promise and then check it calls the convert function on success:

This test now essentially has the SpaceShip being passed in, the entity, and the converted SpaceShip variables. The converter has been refactored to convert to and from entities.  The responses for converting and saving have been mocked to return the expected values. 

The test returns the promise so the then function can be run to test all the mocks have been called correctly. The test currently fails as expected — update the service to call and consume the promise from the repo save and then convert the response back to a SpaceShip

We can now update the test signature to accept the return type:

return service.save(spaceShip).then((returnedSpaceShip: SpaceShip) => {

The next test we want to test that if the promise is rejected the system can handle it. Let’s update the save to throw an exception if there was an error:

Now we have the most basic unit tests complete for the service.

Further Refactor: The Controller

As we did not account for/know of the return type at the start we also need to refactor the controller. If we look at the controller, IntelliJ gives us a hint that Promise returned from save is ignored  so let’s create a breaking test to fix this. 

We simply need to update the controller signature to return a Promise and update the test:

We don’t need to handle and resolve our rejects here as the controller returns whatever the Promise is from the service. All we need to do is check the wiring ie. the service is called with data and returns data.

Service Test Summary

Here we started to see some twists and turns of TDD — going back and forth, refactoring areas here and there. While this may seem like re-work — what we are actually doing is ensuring that the code is testable and in turn maintainable. It’s very common for junior developers to come in and write the code for several days only to finalize the ticket with the start of tests. Then they have to go in a refactor the entirety because un-testable/very difficult to test code has been written.

We achieved some confidence now that the service works as expected. We have extracted the logic to convert between DTOs and Entities. The service will attempt to save the valid data, return back the parsed entity and handle errors with a known exception. A small refactor was carried out from a change not seen until later in the development and was easily factored in due to TDD.

Next up the repository unit testing:

Part 1 — Unit Testing the Controller | Git branch can be found here.

Part 2 — Unit Testing the Service | Git branch can be found here.

Next — Part 3 — Unit Testing the Repository | Git branch can be found here.

Credit: Source link

Previous Next
Close
Test Caption
Test Description goes like this