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()
- describes elements or scenarios and groups closely-related tests togetherit()
- short for individual test, the test itself
The it()
name also makes the test name 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 testsbeforeEach()
- before each testafterEach()
- after each testafterAll()
- after all the tests
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! We're about to begin our first test, but first, let's just see some more fundamental theory.
Tests ideally should follow the AAA pattern, which means:
Arrange: Setup the data necessary to perform both the test and the assertions
Act: Peform the test itself, using an actual element of the system
Assert: Verify if the output of the test is what it should be
Let's then write our first test, for the findOne()
method. It receives an id
and returns a user
, so we'll perform these steps for now, following the AAA pattern:
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