Skip to content

5. Testing

Chabardes edited this page Jun 16, 2022 · 6 revisions

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.

Unit test

Arrange, Act and Assert

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 be ok(...) or err(...) and that our entities have correctly changed.

Dependency injection

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 the Hero entity.
const heroMockAdapter = new HeroMockAdapter();
  • For the second port, because we used the Pick<...> generic, we only need to mock the getHeroInventory 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 !

Create test data

  • Use the heroMockAdapter to create a test hero.
  • Create a test item, you can either use an itemMockAdapter or the itemEntityFactory.
  • 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 a neverthrow Result.

Execute

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.

Assert

  • Check the result of the command, you can use the neverthrow methods isOk() or isErr().
  • 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.

Integration test

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.

Clone this wiki locally