DEV Community

Cover image for SparkyTestHelpers: Scenarios
Brian Schroer
Brian Schroer

Posted on • Edited on

1 1

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)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

AWS Security LIVE!

Hosted by security experts, AWS Security LIVE! showcases AWS Partners tackling real-world security challenges. Join live and get your security questions answered.

Tune in to the full event

DEV is partnering to bring live events to the community. Join us or dismiss this billboard if you're not interested. ❤️