# Supercharged Sagas - Unit Testing Strategies

> Learn how to write tests for your supercharged sagas and strategies for getting the most value out of those tests.

- URL: https://blog.nimblepros.com/blogs/supercharged-sagas-unit-testing-strategies/
- Author: [Kyle McMaster](https://blog.nimblepros.com/authors/kyle/)
- Published: 2024-09-27
- Series: [NServiceBus](https://blog.nimblepros.com/series/nservicebus/)
- Categories: [Software Development](https://blog.nimblepros.com/categories/software-development/)
- Publisher: [NimblePros](https://blog.nimblepros.com/)

---

This post is part of a series on [NServiceBus](https://go.particular.net/using-nservicebus-2024). In the past couple articles, we created a saga that tries to verify a contributor's phone number and marks them accordingly based on their action (or lack thereof). In this post, we'll work through unit testing our saga and demonstrate how to check its behavior. All the code for this series can be found in the [NServiceBusTutorial repository on GitHub](https://github.com/KyleMcMaster/NServiceBusTutorial).

## Testing Sagas ✅

Testing sagas can be a bit tricky. On one hand, we may simply need to verify a specific behavior similar to how we would test [NServiceBus message handlers](https://blog.nimblepros.com/blogs/testing-nservicebus-message-handlers/). On the other hand, Sagas are long-running processes that can span multiple messages or [timeouts](https://blog.nimblepros.com/blogs/supercharged-sagas-timeout-tips/). When testing sagas, we likely need to verify that the saga sends or publishes messages in response to specific events. We may also need to verify that the saga updates its state correctly in response to messages or timeouts. We can assert all of this behavior is working as expected using traditional unit testing approaches and tools from the [NServiceBus.Testing NuGet Package](https://www.nuget.org/packages/NServiceBus.Testing/). Let's take a look at how we can test our saga.

## Testing Saga Message Handlers 📨

When testing individual saga message handlers, we can use the `TestableMessageHandlerContext` from the `NServiceBus.Testing` package to peek into the handlers behavior. This allows us to verify that the saga sends or publishes messages in response to specific events. Referencing our sample saga, in the `Handle` method for `StartContributorVerificationCommand` we send a `VerifyContributorPhoneNumberCommand`. We can verify this behavior in a unit test like so:

```csharp
[Fact]
public async Task ShouldSendVerifyContributorCommand()
{
  var message = new StartContributorVerificationCommand();
  var saga = new ContributorVerificationSaga
  {
    Data = new()
  };
  var context = new TestableMessageHandlerContext();

  await saga.Handle(message, context);

  var sentMessage = context.FindSentMessage<VerifyContributorCommand>();
  Assert.NotNull(sentMessage);
}
```

In the [Arrange step](https://deviq.com/testing/arrange-act-assert) of this test, we initialize a `StartContributorVerificationCommand`, `ContributorVerificationSaga`, and `TestableMessageHandlerContext`. In the Act step, the `Handle` method is invoked on the saga. In the Assert step, we'll inspect the results to check whether a `VerifyContributorCommand` was sent. To make our lives a little easier, we can use the `FindSentMessage` method on `TestableMessageHandlerContext` to search sent messages for a message of a specified type. Finally, we assert that the saga sent a `VerifyContributorCommand` message. This test verifies that the saga sends the correct message in response to a `StartContributorVerificationCommand`.

## Saga Scenario Tests 🧪

When testing sagas, we typically write scenario tests that simulate the behavior of the saga in response to a sequence of messages. These tests help us verify that the saga behaves correctly when it receives messages in a specific order. The `NServiceBus.Testing` package provides a `TestableSaga` class that we can use to write scenario tests for our saga. The `TestableSaga` class allows us to simulate the behavior of the saga, simulate time passing (without using `Thread.Sleep` 😴), and verify the saga's state and behavior. Let's write a scenario test for our saga that verifies the saga's behavior when it receives a `StartContributorVerificationCommand` followed by a `ContributorVerifiedEvent`.

```csharp
[Fact]
public async Task ShouldInitializeSagaAndMarkAsCompleted()
{
  int expectedContributorId = 4680;
  var startCommand = new StartContributorVerificationCommand
  {
    ContributorId = expectedContributorId
  };
  var verifiedEvent = new ContributorVerifiedEvent
  {
    ContributorId = expectedContributorId
  };
  var saga = new TestableSaga<ContributorVerificationSaga, ContributorVerificationSagaData>();
  var context = new TestableMessageHandlerContext();

  var startResult = await saga.Handle(startCommand, context);
  var completeResult = await saga.Handle(verifiedEvent, context);

  Assert.Equal(expectedContributorId, startResult.SagaDataSnapshot.ContributorId);
  Assert.True(completeResult.Completed);
}
```

In this test, we arrange a `StartContributorVerificationCommand`, `ContributorVerifiedEvent`, `TestableSaga`, and `TestableMessageHandlerContext`. Notice how the `TestableSaga` class has generic parameters for the saga class and its `Data` class. In our act step, we invoke the `Handle` method on the saga with the `StartContributorVerificationCommand` and `ContributorVerifiedEvent`. With the `TestableSaga` these `Handle` methods return a snapshot of the saga after the method completes. This allows us to capture the snapshots and write assertions against them. In our Assertion step, we use the `SagaDataSnapshot` property on the `TestableSagaResult` to inspect the saga's state after the `StartContributorVerificationCommand` is handled. We assert that the `ContributorId` in the saga's state matches the expected value. Next, we also assert that the saga is marked as completed after the `ContributorVerifiedEvent` is handled. This test verifies that the saga initializes correctly and marks itself as completed when it receives a `StartContributorVerificationCommand` followed by a `ContributorVerifiedEvent`.

We can also use the `TestableSaga` to simulate time passing in the saga. For example, we can write a test that verifies the saga's behavior when it receives a `StartContributorVerificationCommand` and never receives a `ContributorVerifiedEvent`. We can use the `TestableSaga` to simulate time passing and verify that the saga sends a `MarkContributorAsUnverifiedCommand` after a timeout. In our test, we'll advance time over the 24 hour limit and verify the `ContributorVerificationSagaTimeout` handler produces its side effects as expected. Here's an example of how we can write a test for this scenario:

```csharp
[Fact]
public async Task ShouldTimeoutWhenTimeAdvancesOverLimit()
{
  int expectedContributorId = 4680;
  var startCommand = new StartContributorVerificationCommand
  {
    ContributorId = expectedContributorId
  };
  var saga = new TestableSaga<ContributorVerificationSaga, ContributorVerificationSagaData>();
  var context = new TestableMessageHandlerContext();

  var startResult = await saga.Handle(startCommand, context);
  var timeouts = await saga.AdvanceTime(TimeSpan.FromHours(25));

  Assert.Equal(expectedContributorId, startResult.SagaDataSnapshot.ContributorId);
  var timeoutResult = timeouts.Single();
  Assert.NotNull(timeoutResult.FindSentMessage<NotVerifyContributorCommand>());
  Assert.True(timeoutResult.Completed);
}
```

Our arrangement step is similar to the previous test. We initialize a `StartContributorVerificationCommand`, `TestableSaga`, and `TestableMessageHandlerContext`. In the Act step, we invoke the `Handle` method on the saga with the `StartContributorVerificationCommand`. We then advance time by 25 hours using the `AdvanceTime` method on the `TestableSaga`. This method returns a list of `TestableSagaResult` objects that represent the saga's state after the time has advanced. In the Assert step, we inspect the results to verify that the saga sends a `NotVerifyContributorCommand` message after the timeout. We also assert that the saga is marked as completed after the timeout. 

## Conclusion 🚀

In this post, we learned a few different strategies for writing unit tests for our saga. We covered how to test individual saga message handlers and how to write scenario tests to verify the behavior of the saga in response to a sequence of messages. We used the `TestableMessageHandlerContext` and `TestableSaga` from the `NServiceBus.Testing` package to peek into the saga's behavior and verify that it sends or publishes messages in response to specific events. We also demonstrated how to verify that the saga updates its state correctly in response to messages. These tests can be invaluable in ensuring that our saga behaves as expected and covering complex flows and durations of time. Stay tuned for more helpful tips on using NServiceBus! 👋

## Resources 📚

- [NServiceBus Testing](https://docs.particular.net/nservicebus/testing/)
- [NServiceBus.Testing NuGet Package](https://www.nuget.org/packages/NServiceBus.Testing/)
- [Arrange-Act-Assert](https://deviq.com/testing/arrange-act-assert)
- [NServiceBusTutorial Repository on GitHub](https://github.com/KyleMcMaster/NServiceBusTutorial)
