Ecotone
SponsorBlogGithubSupport and ContactCommunity Channel
  • About
  • Installation
  • How to use
    • CQRS PHP
    • Event Handling PHP
    • Aggregates & Sagas
    • Scheduling in PHP
    • Asynchronous PHP
    • Event Sourcing PHP
    • Microservices PHP
    • Resiliency and Error Handling
    • Laravel Demos
    • Symfony Demos
      • Doctrine ORM
  • Tutorial
    • Before we start tutorial
    • Lesson 1: Messaging Concepts
    • Lesson 2: Tactical DDD
    • Lesson 3: Converters
    • Lesson 4: Metadata and Method Invocation
    • Lesson 5: Interceptors
    • Lesson 6: Asynchronous Handling
  • Enterprise
  • Modelling
    • Introduction
    • Message Bus and CQRS
      • CQRS Introduction - Commands
        • Query Handling
        • Event Handling
      • Aggregate Introduction
        • Aggregate Command Handlers
        • Aggregate Query Handlers
        • Aggregate Event Handlers
        • Advanced Aggregate creation
      • Repositories Introduction
      • Business Interface
        • Introduction
        • Business Repository
        • Database Business Interface
          • Converting Parameters
          • Converting Results
      • Saga Introduction
      • Identifier Mapping
    • Extending Messaging (Middlewares)
      • Message Headers
      • Interceptors (Middlewares)
        • Additional Scenarios
      • Intercepting Asynchronous Endpoints
      • Extending Message Buses (Gateways)
    • Event Sourcing
      • Installation
      • Event Sourcing Introduction
        • Working with Event Streams
        • Event Sourcing Aggregates
          • Working with Aggregates
          • Applying Events
          • Different ways to Record Events
        • Working with Metadata
        • Event versioning
        • Event Stream Persistence
          • Event Sourcing Repository
          • Making Stream immune to changes
          • Snapshoting
          • Persistence Strategies
          • Event Serialization and PII Data (GDPR)
      • Projection Introduction
        • Configuration
        • Choosing Event Streams for Projection
        • Executing and Managing
          • Running Projections
          • Projection CLI Actions
          • Access Event Store
        • Projections with State
        • Emitting events
    • Recovering, Tracing and Monitoring
      • Resiliency
        • Retries
        • Error Channel and Dead Letter
          • Dbal Dead Letter
        • Idempotent Consumer (Deduplication)
        • Resilient Sending
        • Outbox Pattern
        • Concurrency Handling
      • Message Handling Isolation
      • Ecotone Pulse (Service Dashboard)
    • Asynchronous Handling and Scheduling
      • Asynchronous Message Handlers
      • Asynchronous Message Bus (Gateways)
      • Delaying Messages
      • Time to Live
      • Message Priority
      • Scheduling
      • Dynamic Message Channels
    • Distributed Bus and Microservices
      • Distributed Bus
        • Distributed Bus with Service Map
          • Configuration
          • Custom Features
          • Non-Ecotone Application integration
          • Testing
        • AMQP Distributed Bus (RabbitMQ)
          • Configuration
        • Distributed Bus Interface
      • Message Consumer
      • Message Publisher
    • Business Workflows
      • The Basics - Stateless Workflows
      • Stateful Workflows - Saga
      • Handling Failures
    • Testing Support
      • Testing Messaging
      • Testing Aggregates and Sagas with Message Flows
      • Testing Event Sourcing Applications
      • Testing Asynchronous Messaging
  • Messaging and Ecotone In Depth
    • Overview
    • Multi-Tenancy Support
      • Getting Started
        • Any Framework Configuration
        • Symfony and Doctrine ORM
        • Laravel
      • Different Scenarios
        • Hooking into Tenant Switch
        • Shared and Multi Database Tenants
        • Accessing Current Tenant in Message Handler
        • Events and Tenant Propagation
        • Multi-Tenant aware Dead Letter
      • Advanced Queuing Strategies
    • Document Store
    • Console Commands
    • Messaging concepts
      • Message
      • Message Channel
      • Message Endpoints/Handlers
        • Internal Message Handler
        • Message Router
        • Splitter
      • Consumer
      • Messaging Gateway
      • Inbound/Outbound Channel Adapter
    • Method Invocation And Conversion
      • Method Invocation
      • Conversion
        • Payload Conversion
        • Headers Conversion
    • Service (Application) Configuration
    • Contributing to Ecotone
      • How Ecotone works under the hood
      • Ecotone Phases
      • Registering new Module Package
      • Demo Integration with SQS
        • Preparation
        • Inbound and Outbound Adapters and Message Channel
        • Message Consumer and Publisher
  • Modules
    • Overview
    • Symfony
      • Symfony Configuration
      • Symfony Database Connection (DBAL Module)
      • Doctrine ORM
      • Symfony Messenger Transport
    • Laravel
      • Laravel Configuration
      • Database Connection (DBAL Module)
      • Eloquent
      • Laravel Queues
      • Laravel Octane
    • Ecotone Lite
      • Logging
      • Database Connection (DBAL Module)
    • JMS Converter
    • OpenTelemetry (Tracing and Metrics)
      • Configuration
    • RabbitMQ Support
    • Kafka Support
      • Configuration
      • Message partitioning
      • Usage
    • DBAL Support
    • Amazon SQS Support
    • Redis Support
  • Other
    • Contact, Workshops and Support
Powered by GitBook
On this page
  • Testing Aggregates
  • Testing Aggregates with Event Store
  • Testing Projections
  • Acceptance Tests
  • Testing with Real Event Store

Was this helpful?

Export as PDF
  1. Modelling
  2. Testing Support

Testing Event Sourcing Applications

Testing Event Sourcing applications in PHP

PreviousTesting Aggregates and Sagas with Message FlowsNextTesting Asynchronous Messaging

Last updated 1 year ago

Was this helpful?

Ecotone comes with test support for testing Event Sourcing Applications. It provides sensible default and In Memory Event Store and Projection states, so you can implement each part of your event sourced application.

Testing Aggregates

Testing aggregates and checking expected events was described in , however if you would like to extend testing by providing initial list of event, you may do it:

/** Setting up event sourced aggregate initial events */
$this->assertEquals(
    'Andrew',
    EcotoneLite::bootstrapFlowTesting([Ticket::class])
        // 1. setting up initial events for aggregate
        ->withEventsFor($ticketId, Ticket::class, [
            new TicketWasRegistered($ticketId, 'Johny', 'alert'),
            new AssignedPersonWasChanged($ticketId, 'Elvis'),
        ])
        ->sendCommand(new ChangeAssignedPerson($ticketId, 'Andrew'))
        ->getAggregate(Ticket::class, $ticketId)
        ->getAssignedPerson()
);
  1. By using withEventsFor you may provide initial list of events for given aggregate

In Ecotone Flow Testing you're not bounded to given aggregate instance. You may actually run withEventsFor for different identifiers or even different aggregates. This is especially useful, when testing Projections.

Testing Aggregates with Event Store

You may also test your Event Sourced Aggregates and Sagas with In Memory Event Store that will serialize and deserialize, your events.

Testing with In Memory Event Store, ensures your events will be correctly serialized and deserialized. This make the tests more production like and covers your serialization and deserialization with tests.

Let's set up Ecotone Lite for this scenario:

$this->assertEquals(
    'Andrew',
    // 1. Bootstrap with Event Store
    EcotoneLite::bootstrapFlowTestingWithEventStore(
        // 2. Set up Converters classes to resolve
        [Ticket::class, TicketEventConverter::class],
        // 2. Set up Converters instances
        [new TicketEventConverter()]
    )
        ->withEventsFor($ticketId, Ticket::class, [
            new TicketWasRegistered($ticketId, 'Johny', 'alert'),
            new AssignedPersonWasChanged($ticketId, 'Elvis'),
        ])
        ->sendCommand(new ChangeAssignedPerson($ticketId, 'Andrew'))
        ->getAggregate(Ticket::class, $ticketId)
        ->getAssignedPerson()
);
  1. When using bootstrapFlowTestingWithEventStore Ecotone will bootstrap In Memory Event Store for you and provide default configuration for your best development experience.

  2. In case we have some customer converters, this is the place where we can set up them.

If you're using default serialization mechanism ecotone/jms-converter, it will be loaded for you, so all you will need to register is custom Converters, if you have any.

Testing Projections

Projections are following your streams and building read model. Ecotone allows you to rebuild your projections in synchronous way for testing purposes and can keep all the projection's state (like position) in memory.

Testing projections works only with Event Store, so you need to make use of bootstrapFlowTestingWithEventStore.

$this->assertEquals(
    [$productId->toString() => $productPrice],
    EcotoneLite::bootstrapFlowTestingWithEventStore(
            // 1. Setting projection and aggregate that we want to resolve
            [CurrentBasketProjection::class, Basket::class],
            [new CurrentBasketProjection(), new EmailConverter(), new PhoneNumberConverter(), new UuidConverter()],
        )
        // 2. Providing initial events to run projection on
        ->withEventsFor($userId, Basket::class, [
            new ProductWasAddedToBasket($userId, $productId, $productPrice)
        ])
        // 3. Triggering projection
        ->triggerProjection("current_basket")
        // 4. Runing query on projection to validate the state
        ->sendQueryWithRouting(CurrentBasketProjection::GET_CURRENT_BASKET_QUERY, $userId)
);
  1. Setting up projection and related aggregate

  2. Providing initial events that projection will use

  3. After setting up initial events, we may trigger projection

  4. And then we can run query, that will fetch projection's read model

If your projection is asynchronous event driven, then for testing purposes by default it will become synchronous. In this way Ecotone helps in making your tests more readable.

Acceptance Tests

Testing is flexible and not bounded to given class, you may test full application flow, if you want to.

$this->assertEquals(
    [$productId->toString() => $productPrice],
    $this->getTestSupport($emailToken, $phoneNumberToken)
        ->sendCommand(new AddProduct($productId, "Milk", $productPrice))
        ->sendCommand(new RegisterUser($userId, "John Snow", Email::create('test@wp.pl'), PhoneNumber::create('148518518518')))
        ->sendCommand(new VerifyEmail($userId, VerificationToken::from($emailToken)))
        ->sendCommand(new VerifyPhoneNumber($userId, VerificationToken::from($phoneNumberToken)))
        ->sendCommand(new AddProductToBasket($userId, $productId))
        ->sendQueryWithRouting(CurrentBasketProjection::GET_CURRENT_BASKET_QUERY, $userId)
);

On this level we don't even need to interact with Projections, Events, Aggregates directly. We call set of commands and then using an query we can assert the state of the system.

Acceptance tests are the high level tests that rarely change. They focus on interaction with the system like, it would be done by the end user. If acceptance tests pass, it ensures that end-users will be able to work with your system correctly.

Testing with Real Event Store

So far we have been testing using In Memory Event Store, however we can run the test with real event store, if we want to be closer to production run.

$ecotoneLite = EcotoneLite::bootstrapFlowTestingWithEventStore(
            [Ticket::class],
            // Provide connection to the database
            [DbalConnectionFactory::class => new DbalConnectionFactory('pgsql://ecotone:secret@localhost:5432/ecotone')],
            // Tell Ecotone to enable production Event Store
            runForProductionEventStore: true
);

However as it runs against real database instance we may require cleaning things up before each test.

// delete event stream and reset projection
$ecotoneLite->deleteEventStream(Ticket::class);
$ecotoneLite->resetProjection("ticket_list_projection");

We provide list of converters, you may provide your too. We need to set up repository that we will be using for tests.

previous chapter
custom serialization