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));
});
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}
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 */});
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));
Top comments (0)