Some theory & First test
A bit of theory before proceeding further.
Now, let's begin to write the unit tests for the UsersService
. Inside the file users.service.spec, we may then continue our discussion.
Basically, these test files are organized in two main kinds of blocks:
describe()
- describe elements or scenarios and group closely-related tests togetherit()
- short for individual test, the test itself
The it()
name also makes the test more readable: it should do something.
Therefore, as we are testing the methods of the UsersService
, its natural that we have the following organization:
First, everything is put inside a
describe()
block for theUsersService
itselfThen, inside it, we should have a
describe()
for each one of its methodsAfter that, inside each one, we can have a
describe()
for each possible scenarioFinally, inside them, write the
it()
block or the test itself
Very well, but notice the beforeEach()
method. Inside it, logic is executed before each test. Alongside it, there are three other methods that execute at distinct moments:
beforeAll()
- before all the tests, oncebeforeEach()
- before each testafterEach()
- after each testafterAll()
- after all the tests, once
So, what happens here is that, before each test, a module
of type TestingModule
will be initialized with just the UsersService
. After that, a variable for the service
obtains its reference from within the module
. It's done like this because unit tests should be performed in isolation, so we should test only the UsersService
. Any external dependencies should be mocked, that is, have a simplified representation. We will not test the Repository
from TypeORM, we should assume that it works properly because it's not our responsibility to test it. Our focus should be only to test our own code.
Due to this, before each test we have a fresh service
. The first test, which comes by default, is the it should be defined test. It simply expects the service
to be defined. The expect()
function receives an argument which we expect something from, and can be chained with many useful functions to perform different assertions, such as toBeDefined()
in this case.
It will obviously pass, right? Actually, it will fail. This is because the UsersService
relies on the Repository
, which is not included in the module
. But we should not use the actual Repository
, nor connect to a database. As was said, this breaks that isolation principle mentioned earlier. So, at least for now, let's have the repository
as an empty object, in the providers
of the module
. To represent a Repository
here, we can use the getRepositoryToken()
function, passing the entity as argument.
By executing the test again, now it passes! Let's then finally begin our first test, for the findOne()
method. This method receives an id
, so we'll perform these steps for now:
Create a fake
id
and anexpectedUser
Call the method in the
service
Check if the obtained
user
matches theexpectedUser
This will, however, not work because the repository
is an empty object. The service
will attempt to invoke a non-existing method in the repository
, causing the test to fail. How could we solve this? Well, the repository
should have its methods as mocked functions. They represent functions without any actual logic, but this implementation can be mocked for each specific test. Let's then learn how to have a mocked repository.
Last updated