Should & Substitute – Two New Great Friends

· November 5, 2010

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.

Twitter, Facebook