SparkyTestHelpers: Scenarios
Tags: csharp, dotnet, testing, unittesting
Published: 2020-02-19 22:51:37

> [NuGet package](https://www.nuget.org/packages/SparkyTestHelpers/) | [Source code](https://github.com/BrianSchroer/sparky-test-helpers/tree/master/SparkyTestHelpers/Exceptions) | [API documentation](https://github.com/BrianSchroer/sparky-test-helpers/blob/master/SparkyTestHelpers/api.md)

When I started a new job a few years ago where the team used MSTest instead of NUnit, one thing I really missed was "row tests" using [NUnit [TestCase]](https://github.com/nunit/docs/wiki/TestCase-Attribute) attributes to easily write a test than runs against multiple test cases without a lot of duplicate code.

> This has since been added to MSTest with [[DataMemberTest] and [DataRow]](https://www.pmichaels.net/2016/07/23/using-mstest-datarow-as-a-substitute-for-nunit-testcase/) attributes, but since they didn't exist at the time, I've been using my own “scenario testing” helpers, which I've now open-sourced.

My "scenario" test helpers are in the ["SparkyTestHelpers" NuGet package](https://www.nuget.org/packages/SparkyTestHelpers/) (which works with any .NET unit testing framework), and they look like this:

```csharp
using SparkyTestHelpers.Scenarios;
. . .
ForTest.Scenarios
(
    new { DateString = "1/31/2023", ShouldBeValid = true },
    new { DateString = "2/31/2023", ShouldBeValid = false },
    new { DateString = "6/31/2023", ShouldBeValid = false }
)
.TestEach(scenario =>
{
    DateTime dt;
    Assert.AreEqual(
        scenario.ShouldBeValid,
        DateTime.TryParse(scenario.DateString, out dt));
});
```

**ForTest.Scenarios** is just a static syntax helper method that creates an array. The real "magic" is the *IEnumerable<T>*.**TestEach** extension method, which passes each item in the enumerable back into a "callback" test action.

**TestEach** can be "dotted on" to any IEnumerable<T>. You don't have to create your scenarios with **ForTest.Scenarios**, but I think that method makes your code nicely readable. You can use any type for your IEnumerable<T>, including anonymous types and tuples, which is how I usually do it (as shown in the code snippet above).

Scenario exceptions are caught by the **TestEach** ScenarioTester. After all scenarios have been tested, if any were unsuccessful, a **ScenarioTestFailureException** is thrown to fail your "scenario suite" test method. The exception message includes details about which scenario(s) failed:

```
Test method MyNamespace.UnitTests.DateTests threw exception:
SparkyTestHelpers.Scenarios.ScenarioTestFailureException: Scenario[1] (2 of 3) 
- Assert.AreEqual failed. Expected:<True>. Actual:<False>.

Scenario data - anonymousType: {"DateString":"2/31/2023","ShouldBeValid":true}

Scenario[2] (3 of 3) - Assert.AreEqual failed. Expected:<True>. Actual:<False>.

Scenario data - anonymousType: {"DateString":"4/31/2023","ShouldBeValid":true}
```

Per-scenario test setup/teardown actions can be specified via **BeforeEachTest** and **AfterEachTest**. You can use these for tasks like logging or resetting mocks:

```csharp
ForTest.Scenarios(array)
    .BeforeEachTest(scenario => {})
    .AfterEachTest(scenario => {})
    .TestEach(scenario => { /* test logic */});
```

The library also has helpers for testing with all **EnumValues** for an enum type (or all **ExceptFor** one or more that you want to bypass):

```csharp
ForTest.EnumValues<OrderStatus>()
    .TestEach(orderStatus => foo.Bar(orderStatus));

ForTest.EnumValues<OrderStatus>()
    .ExceptFor(OrderStatus.Cancelled, OrderStatus.Rejected)
    .TestEach(orderStatus => foo.Bar(orderStatus));
```