Testing an Event Sourced application

Your ads will be inserted here by

Easy Plugin for AdSense.

Please go to the plugin admin page to
Paste your ad code OR
Suppress this ad slot.

Testing an Event Sourced application

Some time ago I’ve published a sample application showing how to build a simple event sourced application using Rails & RES. But there was a big part missing there – the tests.

My sample uses CQRS approach to handle all operations.

That means the control flow is as follow:

  • A command is created based on params from UI
  • Command is handled by a command handler:
    • based on command’s aggregate id all events for an aggregate are loaded from RES and aggregate state is recreated
    • a domain object method is called that will produce new domain events
    • domain event are applied to the aggregate
    • domain events are stored in RES & published to event handlers

AAA

This is a basic pattern how good test should be created. There are 3 parts: Arrange – when you setup initial state for a test, Act – where you perform actual operation you want to test and Assert – when you check results.

And the AAA pattern should be preserved for Event Sources application.

Given a series of events

How to build an initial state when you don’t have a state?

This should be quite easy. Any state is a derivative of domain events. You could build any state by applying domain events.

To build a state you just need some events:

Your ads will be inserted here by

Easy Plugin for AdSense.

Please go to the plugin admin page to
Paste your ad code OR
Suppress this ad slot.

include CommandHandlers::TestCase  test 'order is created' do   event_store = FakeEventStore.new   aggregate_id = SecureRandom.uuid   customer_id = 1   order_number = "123/08/2015"   arrange(event_store,     [Events::ItemAddedToBasket.create(aggregate_id, customer_id)])   # ... end  # ./test/lib/command_handlers/test_case.rb module CommandHandlers   class FakeEventStore     def initialize       @events = []       @published = []     end      attr_reader :events, :published      def publish_event(event, aggregate_id)       events << event       published << event     end      def read_all_events(aggregate_id)       events     end   end    class FakeNumberGenerator     def call       "123/08/2015"     end   end    module TestCase     # ...     def arrange(event_store, events)       event_store.events.concat(events)     end     # ...   end end 

Then we have our test state arranged. Notice that I’ve used fake event store & domain services to avoid dependencies and have really fast tests.

When a command

In Event Sourced application act (operation we want to test) is usually handling of a command. To do it you just need a command, you need the command handler and then just dispatch the command to the command handler.

test 'order is created' do   # ...   act(event_store,     Command::CreateOrder.new(order_id: aggregate_id, customer_id: customer_id))   # ... end  # ./test/lib/command_handlers/test_case.rb module CommandHandlers   # ...   module TestCase     include Command::Execute      # ...     def act(event_store, command)       execute(command, **dependencies(event_store))     end      # ...     private     def dependencies(event_store)       {         repository:           RailsEventStore::Repositories::AggregateRepository.new(event_store),         number_generator:           FakeNumberGenerator.new       }     end   end end 

The same Command::Execute module is used in ApplicationController to dispatch real commands to the system.

Expect a series or events

You should not assert on the current state, actually you should not rely on a state at all. All you need to verify is if the correct domain events have been produced.

test 'order is created' do   # ...   assert_changes(event_store,     [Events::OrderCreated.create(aggregate_id, order_number, customer_id)]) end  # ./test/lib/command_handlers/test_case.rb module CommandHandlers   # ...   module TestCase     # ...      def assert_changes(event_store, expected)       actuals = event_store.published.map(&:data)       expects = expected.map(&:data)       assert_equal(actuals, expects)     end      def assert_no_changes(event_store)       assert_empty(event_store.published)     end   end end 

And because all state is a result of events checking what have been produced has a nice side effect. You test if all expected domain events have been produced and if only the ones expected. In that case, you test if any unexpected change have not been introduced.

or an exception

Remember that any command may end up with an error. There could be various reasons, technical ones (oh no! regression again?), or error could be just a result of some business rules validations.

Complete code sample for blog post could be found here.

Leave a Reply

Your email address will not be published. Required fields are marked *