-
Notifications
You must be signed in to change notification settings - Fork 1
5. Testing
If you took a look at the commits, you may have noticed that the commit for the equip-item command contained a .spec
file. This is a unit test for our handler. Let's dive a bit into that.
Usually, each unit test follow the AAA pattern (Arrange, Act, Assert).
- During the Arrange phase, we setup all that we need for our test: mock adapters and entities.
- During the Act phase, we call our handler execute method with the right payload.
- During the Assert phase, we check that the result is what we expected: a
neverthrow
monad that should either beok(...)
orerr(...)
and that our entities have correctly changed.
NestJS makes a heavy use of the dependency injection design pattern. At runtime, our EquipItemCommandHandler
will receive the correct adapters for its ports thanks to the provider mapping declared in HeroInfrastructure
. But during a test, we can use the dependency injection to provide mock adapters for those ports.
If we check the handler constructor, we can see that we need to provide two adapters: one for heroPorts: UpdatePort<Hero> & GetByIdPort<Hero>
and one for itemPresenter: Pick<ItemPresenter, 'getHeroInventory'>
.
- Because heroPorts only use the basic CRUD ports, we can use the existing
HeroMockAdapter
. This mock adapter already has all the CRUD operations (create, getAll, getById, update, and delete) for theHero
entity.
const heroMockAdapter = new HeroMockAdapter();
- For the second port, because we used the
Pick<...>
generic, we only need to mock thegetHeroInventory
method.
const itemMockPresenter = {
getHeroInventory: jest.fn(),
}
Now that we have all our mock adapters, we can instantiate our command handler.
const equipItemCommandHandler = new EquipItemCommandHandler(
heroMockAdapter,
itemMockPresenter,
);
We are almost ready for the Act phase !
- Use the heroMockAdapter to create a test hero.
- Create a test item, you can either use an
itemMockAdapter
or theitemEntityFactory
. - Call
itemMockPresenter.getHeroInventory.mockResolvedValueOnce
to set what should be returned by our mock method. depending on our test scenario, it could return an empty inventory or an array containing our test item. Don't forget to wrap it in aneverthrow Result
.
Now we can do the Act part of our test ! Create a EquipItemCommand
with the test data as payload, and call the execute method of our equipItemCommandHandler
.
- Check the result of the command, you can use the
neverthrow
methodsisOk()
orisErr()
. - Check that the entities have been correctly modified.
Your unit test is done ! You should be able to test all the possible output of the handler. Don't forget to reset your mock adapters between each tests. Take a look at this file if you need help.
In our backend integration tests, we follow the same AAA pattern.
During the Arrange phase, instead of mocking adapters, we mock a whole NestJs module (More info on NestJs testing), and we use the connection manager to directly add data in a test database.
During the Act phase, instead of directly calling the execute method of a handler, we will call an interface (in this case we will make a GraphQL request).
And during the Assert phase, we will check the response from the interface and check the state of our test database with the connection manager.
Take a look at heroes/tests/heroes.e2e-spec.ts
, then try and write a new test for the feature we just created. The relevant commit.
Integration tests are more comprehensive than unit tests, and are most useful when testing features involving several queries/commands.