API with NestJS #87. Writing unit tests in a project with raw SQL

JavaScript NestJS SQL

This entry is part 87 of 180 in the API with NestJS

Writing tests is crucial when aiming to develop a solid and reliable application. In this article, we explain the idea behind unit tests and write them for our application that works with raw SQL queries.

The idea behind unit tests

The job of a unit test is to make sure that an individual part of our application works as expected. Every test should be isolated and independent.

authentication.service.test.ts

PASS src/authentication/authentication.service.test.ts
The AuthenticationService
when calling the getCookieForLogOut method
✓ should return a correct string

Above, you can see that we use the constructor of the class. While we can provide all necessary dependencies manually, as in the example above, NestJS provides some utilities to help us.

By using the method, we create a testing module. By doing that, we mock the entire NestJS runtime. Then, when we run its method, we bootstrap the module with its dependencies in a similar way that our file works.

authentication.service.test.ts

Mocking the database connection

There is a very significant problem with the above test suite. Importing our class causes our application to try to connect to an actual database. This is something we definitely want to avoid when writing unit tests.

Simply removing the from the array causes the following error:

Error: Nest can’t resolve dependencies of the UsersRepository (?). Please make sure that the argument DatabaseService at index [0] is available in the UsersModule context.

We need to acknowledge that the uses the database under the hood. To solve this problem, we can provide a mocked version of the class that does not use a real database.

It might be a good idea to avoid mocking whole modules when writing unit tests. This is because e don’t want to test how modules and classes interact with each other just yet.

authentication.service.test.ts

PASS src/authentication/authentication.service.test.ts
The AuthenticationService
when calling the getCookieForLogOut method
✓ should return a correct string
when registering a new user
and when the usersService returns the new user
✓ should return the new user

Thanks to mocking the , we are confident our tests won’t need the real database.

Changing the mock per test

In the above test, we always assume that the method of the returns a valid user. However, this is not always the case.

There are two major cases for the methods:

  • it returns the created user if there weren’t any problems with the data,
  • it throws an error if the user with a given email address already exists.

Fortunately, we can change our mock per test. However, to do that, we need to ensure that the mock is accessible through every test. We can achieve that by creating a variable that we modify through the hook.

Thanks to using we ensure that each test is independent and does not affect the other tests.

authentication.service.test.ts

Thanks to creating the variable accessible in the whole test suite, we now have full control over it. We can now manipulate it to cover both of the above cases.

authentication.service.test.ts

PASS src/authentication/authentication.service.test.ts
The AuthenticationService
when calling the getCookieForLogOut method
✓ should return a correct string
when registering a new user
and when the usersService returns the new user
✓ should return the new user
and when the usersService throws the UserAlreadyExistsException
✓ should throw the BadRequestException

Above, we are covering two separate cases:

  • when the returns the new user,
  • when the throws an error.

The crucial thing to notice is that we are not testing the . Our tests focus solely on the methods of the class, which is the essence of the unit tests. When writing unit tests, we don’t verify how classes work together but rather ensure they perform in isolation.

Testing the data repository

So far, in this article, we didn’t test a class that directly uses our . Let’s test the method of our .

users.repository.test.ts

PASS src/users/users.repository.test.ts
The UsersRepository class
when the create method is called
and the database returns valid data
✓ should return an instance of the UserModel
✓ should return the UserModel with correct properties
and the database throws the UniqueViolation
✓ should throw the UserAlreadyExistsException exception

Above, we are testing two crucial cases of the method:

  • the user is successfully added to the database,
  • the database fails to add the user due to the unique violation error.

Feel free to take it a step further and test if the makes the right SQL query.

Summary

In this article, we’ve learned what unit tests are and how to implement them with NestJS. We’ve focused on testing the parts of our application that communicate with a database. Since unit tests shouldn’t use a real database connection, we’ve learned how to mock our services and repositories. There is still more to learn when it comes to testing NestJS when using SQL, so stay tuned!

Series Navigation<< API with NestJS #86. Logging with the built-in logger when using raw SQLAPI with NestJS #88. Testing a project with raw SQL using integration tests >>
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments