★ Implementing event sourcing: testing aggregates
Tags: php, laravel, eventsourcing
Published: 2020-01-02 23:33:15

---
title: ★ Implementing event sourcing: testing aggregates
published: true
date: 2019-07-19 13:17:09 UTC
tags: php, laravel, eventsourcing
canonical_url: https://freek.dev/1409-implementing-event-sourcing-testing-aggregates
---

Earlier this year [we released v2 of laravel-event-sourcing](https://freek.dev/1313-laravel-event-projector-v2-has-been-released). This package is probably the easiest way to getting started with event sourcing in Laravel. A significant feature of v2 was the addition of [aggregates](https://docs.spatie.be/laravel-event-projector/v2/using-aggregates/writing-your-first-aggregate/).

Today we released another new version of the package that adds test methods. [These methods](https://docs.spatie.be/laravel-event-projector/v2/using-aggregates/testing-aggregates/) allow you to verify if the aggregate behaves correctly. In this post, I'll show you an example and explain how the test methods are implemented.

These test methods were inspired by [the awesome testing methods](https://eventsauce.io/docs/testing/) [Frank De Jonge](https://twitter.com/frankdejonge/) made in his Eventsauce package.

<!--more-->
## Testing an aggregate #

An aggregate is a class that decides to record events based on past events. To know more about their general purpose and the idea behind them, read this section on [using aggregates to make decisions-based-on-the-past](https://docs.spatie.be/laravel-event-projector/v2/getting-familiar-with-event-sourcing/using-aggregates-to-make-decisions-based-on-the-past). In the remainder of the post, we're going to assume that you know how to work with aggregates.

Imagine you have an `AccountAggregateRoot` that handles adding and subtract an amount for a bank account. The account has a limit of -$5000.

```
use Spatie\EventProjector\AggregateRoot;

class AccountAggregateRoot extends AggregateRoot
{
    /** @var int */
    private $balance = 0;

    /** @var int */
    private $accountLimit = -5000;

    public function createAccount(string $name, string $userId)
    {
        $this->recordThat(new AccountCreated($name, $userId));

        return $this;
    }

    public function addMoney(int $amount)
    {
        $this->recordThat(new MoneyAdded($amount));

        return $this;
    }

    protected function applyMoneyAdded(MoneyAdded $event)
    {
        $this->balance += $event->amount;
    }

    public function subtractMoney(int $amount)
    {
        $this->hasSufficientFundsToSubtractAmount($amount)
            ? $this->recordThat(new AccountLimitHit($amount))
            : $this->recordThat(new MoneySubtracted($amount));
    }

    protected function applyMoneySubtracted(MoneySubtracted $event)
    {
        $this->balance -= $event->amount;
    }

    private function hasSufficientFundsToSubtractAmount(int $amount): bool
    {
        return $this->balance - $amount >= $this->accountLimit;
    }
}
```

Let's now test that rule that an account cannot go beyond its limit.

```
// in a PHPUnit test

/** @test */
public function it_can_subtract_money()
{
    AccountAggregateRoot::fake()
        ->given(new SubtractMoney(4999))
        ->when(function (AccountAggregate $accountAggregate) {
            $accountAggregate->subtractMoney(1);
        })
        ->assertRecorded(new MoneySubtracted(1))
        ->assertNotRecorded(AccountLimitHit::class);
}

/** @test */
public function it_will_not_make_subtractions_that_would_go_below_the_account_limit()
{
    AccountAggregateRoot::fake()
        ->given(new SubtractMoney(4999))
        ->when(function (AccountAggregate $accountAggregate) {
            $accountAggregate->subtractMoney(2);
        })
        ->assertRecorded(new AccountLimitHit(2))
        ->assertNotRecorded(MoneySubtracted::class);
}
```

You could write the above test a bit shorter. The given events can be passed to the `fake` method. You're also not required to use the `when` function.

```
/** @test */
public function it_will_not_make_subtractions_that_would_go_below_the_account_limit()
{
    AccountAggregateRoot::fake(new SubtractMoney(4999))
        ->subtractMoney(2)
        ->assertRecorded(new AccountLimitHit(2))
        ->assertNotRecorded(MoneySubtracted::class);
}
```

## Implementing aggregate test methods #

Before starting implementing these test methods, I thought it was going to be a bit daunting. It turns out, it was not that hard.

To create an aggregate, you need to let a class extend our `Spatie\EventProjector\AggregateRoot` class.

```
class AccountAggregateRoot extends AggregateRoot {}
```

To keep the implementation of `AggregateRoot` clean, I wanted to avoid adding assertions on that class itself. The only method I added was `fake`. Calling this class will return a new `FakeAggregateRoot` instance in which the aggregate under test (in our example `AccountAggregateRoot`) is encapsulated.

Here is the implementation take from [`AggregateRoot`](https://github.com/spatie/laravel-event-projector/blob/bfa2b4c9e15a5b2ba7f016584f07a81074856e37/src/AggregateRoot.php#L87-L97).

```
/**
 * @param \Spatie\EventProjector\ShouldBeStored|\Spatie\EventProjector\ShouldBeStored[] $events
 *
 * @return $this
 */
public static function fake($events = []): FakeAggregateRoot
{
    $events = Arr::wrap($events);

    return (new FakeAggregateRoot(app(static::class)))->given($events);
}
```

In the `FakeAggregateRoot` all the test methods like `given`, `when`, `assertRecorded` and so on live.

Let's take a look at the simplest one: `assertNothingRecorded`.

```
public function assertNothingRecorded()
{
    PHPUnit\Framework\Assert::assertCount(0, $this->aggregateRoot->getRecordedEvents());
  
    return $this;
}
```

In the method above, we fetch the recorded events of the encapsulated aggregate root. A PHPUnit assertion is used to determine if that array is empty. If it is not, the PHPUnit test will fail.

If you're interested, you can take a look at [the implementation of the other test methods](https://github.com/spatie/laravel-event-projector/blob/master/src/FakeAggregateRoot.php).

## In closing #

I hope you've enjoyed this little tour of the new test methods. Previously I've written two more posts about the implementation details of the package: here's how [the aggregates are implemented](https://freek.dev/1326-implementing-event-sourcing-aggregates) and here's a post on [how the developer experience was improved](https://freek.dev/1329-implementing-event-sourcing-improving-the-developer-experience).

To know more about the package in general read [the introductionary post](https://freek.dev/1313-laravel-event-projector-v2-has-been-released) or [the documentation](https://docs.spatie.be/laravel-event-projector/v2/introduction/).

Be sure to also check out [this big list of PHP & Laravel packages](https://spatie.be/open-source/packages) [our team](https://spatie.be) has made previously.