DEV Community

Cover image for SparkyTestHelpers: Scenarios
Brian Schroer
Brian Schroer

Posted on • Edited on

SparkyTestHelpers: Scenarios

NuGet package | Source code | API documentation

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] 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] 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 (which works with any .NET unit testing framework), and they look like this:

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));
});
Enter fullscreen mode Exit fullscreen mode

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

TestEach can be "dotted on" to any IEnumerable. 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, 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}
Enter fullscreen mode Exit fullscreen mode

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

ForTest.Scenarios(array)
    .BeforeEachTest(scenario => {})
    .AfterEachTest(scenario => {})
    .TestEach(scenario => { /* test logic */});
Enter fullscreen mode Exit fullscreen mode

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):

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

ForTest.EnumValues<OrderStatus>()
    .ExceptFor(OrderStatus.Cancelled, OrderStatus.Rejected)
    .TestEach(orderStatus => foo.Bar(orderStatus));
Enter fullscreen mode Exit fullscreen mode

Top comments (0)