Recently, I discovered two fantastic frameworks that have significantly improved my test code: Should
and NSubstitute
.
Should I? Yes – You Should
First up is Should, an assertion framework that enhances the readability and ease of writing assertions. Here’s an example showcasing its capabilities:
object obj = new object();
obj.Should().Be.OfType(typeof(object));
obj.Should().Equal(obj);
obj.Should().Not.Be.Null();
obj.Should().Not.Be.SameAs(new object());
obj.Should().Not.Be.OfType<string>();
obj.Should().Not.Equal("foo");
"This String".Should().Contain("This");
"This String".Should().Not.Be.Empty();
"This String".Should().Not.Contain("foobar");
var list = new List<object>();
list.Should().Count.Zero();
list.Should().Not.Contain.Item(new object());
var item = new object();
list.Add(item);
list.Should().Not.Be.Empty();
list.Should().Contain.Item(item);
One of the great features of Should
is that it’s not tied to any specific testing framework, making it versatile and compatible with any test framework.
Get Me a Substitute! Now
The other framework I’d like to highlight is NSubstitute, a mocking framework that simplifies creating substitutes for dependencies. The introductory message from NSubstitute says it all:
Mock, stub, fake, spy, test double? Strict or loose? Nah, just substitute for the type you need!
I often mix up mocks, stubs, and fakes, and honestly, I don’t care about the distinctions when I just need a simple substitute. NSubstitute offers clean and easy-to-read syntax. Here’s an example from their site:
// Create a substitute:
var calculator = Substitute.For<ICalculator>();
// Set a return value:
calculator.Add(1, 2).Returns(3);
Assert.AreEqual(3, calculator.Add(1, 2));
// Check received calls:
calculator.Received().Add(1, Arg.Any<int>());
calculator.DidNotReceive().Add(2, 2);
Come Together, Right Now
The real power comes when you combine Should
and NSubstitute
. Here’s an example demonstrating their use with SpecFlow:
[Given(@"the following features in the database:")]
public void GivenTheFollowingFeaturesInTheDatabase(Table table)
{
var features = table.CreateSet<Feature>().ToList();
var dbWrapperSubstitute = Substitute.For<IFeatureDBWrapper>();
// Configure the substitute as needed
}
[When(@"I navigate to the homepage")]
public void WhenINavigateToTheHomepage()
{
var featureDBWrapper = ScenarioContext.Current.Get<IFeatureDBWrapper>(DBWRAPPER_KEY);
var controller = new HomeController(featureDBWrapper);
var indexView = controller.Index() as ViewResult;
indexView.Should().Not.Be.Null();
indexView.ViewData.Model.Should().Not.Be.Null();
var features = (IList<Feature>)indexView.ViewData.Model;
features.Should().Not.Be.Null();
featureDBWrapper.Received().AllNotDone();
ScenarioContext.Current.Set(features, FEATURES_IN_VIEW_KEY);
}
[Then(@"I should see a view with the following features:")]
public void ThenIShouldSeeAViewWithTheFollowingFeatures(Table table)
{
var features = ScenarioContext.Current.Get<IList<Feature>>(FEATURES_IN_VIEW_KEY);
var expectedFeatures = table.CreateSet<Feature>();
foreach (var expectedFeature in expectedFeatures)
{
features.Should().Contain.Any(f => f.Name == expectedFeature.Name);
features.Should().Contain.Any(f => f.AssignedTo == expectedFeature.AssignedTo);
features.Should().Contain.Any(f => f.HoursWorked == expectedFeature.HoursWorked);
features.Should().Contain.Any(f => f.Status == expectedFeature.Status);
features.Should().Contain.Any(f => f.Size == expectedFeature.Size);
}
}
You can find the complete solution here.
Both Should
and NSubstitute
have greatly enhanced my testing capabilities, and I’m excited to see how they can benefit your projects as well.