Alexey Shimansky
Alexey Shimansky

Reputation: 632

How to test and what should be tested for CommandHandler in CQRS?

I have a bunch of handlers. Every of them, eventually, contains code for saving data and releasing events.

How to test CommandHandler? What should be tested? For now it is unit tests, maybe it should be integrational? Should I mock anything or not? Why? Should it be tested at all?

UPD: please, give and show YOUR practical examples of tests and YOUR approaches to it with explanation why. On these examples want to see what to use and how to test.


My code at the moment:

Code of handlers:

public function __invoke(AddCategory $command): int
{
    // some code

    $this->categories->save($category);
    $this->events->publishAll($category->releaseEvents());

    return $category->id()->value;
}

public function __invoke(RenameCategory $command): void
{
    // some code

    $this->categories->save($category);
    $this->events->publishAll($category->releaseEvents());
}

At the moment my tests look like that:

Test addition

protected function setUp(): void
{
    $this->categories = new TestCategoryRepository();
    $this->events     = new TestEventDispatcher();
    $this->handler    = new AddCategoryHandler($this->categories, $this->events);
}

public function testHandler(): void
{
    // preparing code

    ($this->handler)($command);

    $this->assertTrue($currentEventsCountMoreThanInitialEventsCount);
    $this->assertTrue($currentRepositoryCountMoreThanInitialRepositoryCount);
    $this->assertIsInt($categoryId);
    $this->assertNotNull($category);
}

Test some changes:

protected function setUp(): void
{
    $this->categories = $this->createMock(CategoryRepository::class);
    $this->events     = new TestEventDispatcher();
    $this->handler    = new RenameCategoryHandler($this->categories, $this->events);
}

public function testHandler(): void
{
    // preparing code

    $this->categories->expects($this->once())
                     ->method('getById')
                     ->with($categoryId)
                     ->willReturn($category);

    $this->categories->expects($this->once())->method('save');

    ($this->handler)($command);

    $this->assertTrue($currentEventsCountMoreThanInitialEventsCount);
}

Upvotes: 2

Views: 91

Answers (3)

Tore Nestenius
Tore Nestenius

Reputation: 19961

This is my style of testing as shown on my site at cqrs.nu

Why?

  • We can work test driven, writing tests first
  • We do all in code
  • Minimal cermony

You can also test the read-side in a similar manor. More tests can be found here.

public void OrderedDrinksCanBeServed()
  {
        Test(
            Given(new TabOpened
        {
          Id = testId,
          TableNumber = testTable,
          Waiter = testWaiter
        },
          new DrinksOrdered
        {
          Id = testId,
          Items = new List<OrderedItem>
          {testDrink1, testDrink2}
        }),
          When(new MarkDrinksServed
        {
          Id = testId,
          MenuNumbers = new List<int>
          {testDrink1.MenuNumber,testDrink2.MenuNumber}
        }),
          Then(new DrinksServed
        {
          Id = testId,
          MenuNumbers = new List<int>
          {testDrink1.MenuNumber,testDrink2.MenuNumber}
        }));
        }
        ---        
        [Test]
        public void CanNotServeAnUnorderedDrink()
        {
          Test(
            Given(new TabOpened
        {
          Id = testId,
          TableNumber = testTable,
          Waiter = testWaiter
        },
          new DrinksOrdered
        {
          Id = testId,
          Items = new List<OrderedItem>
          {testDrink1}
        }),
          When(new MarkDrinksServed
        {
          Id = testId,
          MenuNumbers = new List<int>
          {testDrink2.MenuNumber}
        }),
          ThenFailWith <DrinksNotOutstanding>());
        }

Testing the read side can look like this:

[Test]
public void GateAssignmentsIncludedInResult()
{
    Test(
        BuildFrom(depSchedEvent1, gateAssignedEvent1),
        rs =>
        {
            var summary = rs.GetUpcomingFlights(1).First();
            Assert.AreEqual(gateAssignedEvent1.Gate, summary.Gate);
            Assert.IsFalse(summary.GateOpen);
        });
}

To summarize:

enter image description here

Upvotes: 1

VoiceOfUnreason
VoiceOfUnreason

Reputation: 57307

What should be tested?

Any part of your system that could have a fault should be tested (or documented as a known risk to be addressed by some other techniques; code inspections, formal proofs, etc).

We tend to lean into automated checks where they are reliable and cost effective; the silicon doesn't get bored of checking the same assertions over and over again.

public function __invoke(AddCategory $command): int
{
    // some code

    $this->categories->save($category);
    $this->events->publishAll($category->releaseEvents());

    return $category->id()->value;
}

Here's a riddle: if events->publishAll is a thing that can fail (because it's a side effect, and the remote system might be unavailable?), what is supposed to happen? Should we still see the side effects of categories->save? should the caller still be receiving an category->id value? etc.

One of the advantages of exercising code in a controlled environment is that you can explore the state space of failures that are difficult/expensive to reproduce in a "real" environment.

Automated checks can serve as a sort of evidence that "yes, we've thought about these possibilities."

In my experience, "unit" vs "integration" isn't a particularly useful distinction; what we really care about are things like cost to run, stability, sensitivity. If the labels help you to communicate with your audience which combinations of trade offs you are referring to - great, do that. But we don't really have a "standardized" understanding of unit test that has been adopted everywhere, so maybe enumerate the trade offs also.

Upvotes: 1

hakre
hakre

Reputation: 198204

One of the noteworthy parts in your design of your CQRS command handlers are the simplicity to make them represent a callable. So one handler stands for itself and is easy to use, just by a function call.

I would mirror that in the test-suite so that tests and implementations are balanced. That is, there is one test-method for each handler invocation.

Taking your example:

public function testHandler(): void
{
    // preparing code

    ($this->handler)($command);

    $this->assertTrue($currentEventsCountMoreThanInitialEventsCount);
    $this->assertTrue($currentRepositoryCountMoreThanInitialRepositoryCount);
    $this->assertIsInt($categoryId);
    $this->assertNotNull($category);
}

it already shows the beginning of that but there is a caveat: It does not show how the handler has been set-up. I'd made this part of the test method so you can see at first glance when you look into a single test what is going on here.

The refactoring is simple: Instead of setting things up in setUp() and assigning to test-case properties, it's all in the local scope:

public function testHandler(): void
{
    // preparing
    $testCategories = new TestCategoryRepository();
    $testEvents     = new TestEventDispatcher();
    $testHandler    = new AddCategoryHandler($testCategories, $tsetEvents);
    $testCommand    = new TestCommand();

    // execution
    ($testHandler)($testCommand);

    // assertion
    $this->assertTrue($currentEventsCountMoreThanInitialEventsCount);
    $this->assertTrue($currentRepositoryCountMoreThanInitialRepositoryCount);
    $this->assertIsInt($categoryId);
    $this->assertNotNull($category);
}

As now a test-method shows the full picture, you already make more visible how much setup a certain handler-test requires. You can more easily compare with a second test by diffing test method bodies only, no need to share a set up and then negotiate use and execution order of shared things nor to look them up.

Now you may think, oh, this is creating all the same code over and over again, and if that is, then you will start to see that often it is very similar but different.

So if things grow, you will be able to better find out, which tests, what the collaborators are, what you actually want to assert and experiment better, e.g. with use of mocks.

I would start extracting test-helper functions relatively late (e.g. starting with one handler test case first, that is about a single handler, writing the first test, running it, committing it, writing the second test, running it, committing it, writing the third test, running it, committing it).

The main reason is to mirror the handler design, it is command focused, so each single test method represents the assertion of one command invocation.

When set-up and assertion code explodes, you practically directly see that this test is testing too much and you easily find the right testing for handler collaborators that are independent to the commands assertions while on the other hand when you implement a command, you are already with confidence because you know that the collaborators are already under test.

Additionally I personally love it when I can compare things with each other easily, this is perhaps how testing works anyway, so why not benefit of that for the test-code already?

Take a step back and think about what you actually need to test. You need to be able to write tests quickly, the tests need to execute quickly and this all should be well integrated in your development workflow.

Write tests first because then you will automatically do instead of ask, and the moment you refactor you are answering your questions already - the tests are just a tool that can assist you.

make test && git commit -am "working" || git reset --hard # <- learn git the "hard" way

Have fun!

Upvotes: 1

Related Questions