Switching to NSubstitute

This post briefly describes the rationale behind changing my preferred isolation/mocking framework from Rhino Mocks to NSubstitute. I’m also including a kind of quick reference section for describing common testing scenarios involving NSubstitute.

Why use an isolation framework?

Here are two object-oriented software development practices you hear about fairly regularly:

  1. Following the dependency inversion principle
  2. Coding to an interface

When following these practices, you typically end up having several interfaces on which your code depends. You can then create “fake” implementations of those interfaces so that you can control what they do; this control allows you to reduce the degrees of freedom when testing your code.

Let’s consider an example scenario where you have some code that loads a customer’s account from a database. For reasons I won’t go into here, you don’t want a unit test to make an actual database call; also maybe you want to see what happens when the account-retrieving functionality doesn’t work as expected (e.g., returns null or throws an exception).

I first learned how to control these dependencies from Roy Osherove’s book The Art of Unit Testing (published 2009). He describes how you can, at first, hand-write special implementations of interfaces to do precisely what you want. Eventually this becomes cumbersome through lots of extra typing and class files all over your unit test project that are typically only used for a handful of tests. The solution is to have an isolation/mocking framework do the grunt work for you.

The purpose of this framework is to let you, the developer, tell it what you’d like these fake objects to do and define how the code-under-test is supposed to be interacting with these objects. For pretty much every major object-oriented language, you can find an open-source isolation framework. The two Osherove mentioned were Moq and Rhino Mocks. I spent several days trying both and found Moq’s syntax clunkier than Rhino Mocks, so I chose the latter.

Why the change of heart?

Rhino Mocks is not without it’s eccentricities, mind you. At the time I was getting up to speed, the documentation left much to be desired. I created my own demo project to learn what Rhino Mocks was capable of, which was primarily derived from looking through the test suite for Rhino Mocks itself. However, there were Stack Overflow questions about it and even a Pluralsight course dedicated to it, so I didn’t feel too bad for picking this particular horse.

Recently, Osherove released a second edition of his book. One of the changes he blogged about was a pretty strong statement about moving away from Rhino Mocks. I consider Osherove an industry expert, and he had just instilled doubt in me regarding my decision. Next, I looked at what he was suggesting as a replacement: NSubstitute.

I followed the same process as before, using a demo project to check out how NSubstitute stacks up to Rhino Mocks. There seemed to be a lot less ceremony for me to accomplish equivalent test arrangements. A few other features which sold me…

  • Not having to specify whether a fake is a stub or a mock in the code. (I continue to make the mental distinction, but it’s hard for people to grok, plus you have to retain information about when to use which type of fake.)
  • Having fakes within fakes is super simple with “chained substitutes.” No more intermediate setup code!
  • Getting helpful failure messages when your unit tests fail. You actually receive information about what happened that didn’t match your expectations. Goodbye “Expected #1, Actual #0”!

In short, it was Osherove’s statement combined with the trend with my dev team at work where they knew what to test, but couldn’t figure out how to wrangle Rhino Mocks into compliance.

Testing scenario reference

This section is intended as a reference guide. Check out this starting test class in my demo project for more of a deep-dive into what NSubstitute can help you isolate.

Disclaimer: Check out the documentation for more obscure scenarios. There may be cases where you need to employ stranger techniques (e.g., clearing received calls, not calling a virtual base method), but they should be rare. Some of those cases are part of my demo project as well.

// Creating a fake
var calc = Substitute.For<ICalculator>();

// Returning a value for specific arg
calc.Add(1, 2).Returns(3);  // <-- methods
calc.Mode.Returns("Hex");   // <-- properties

// Ignoring an argument
calc.Add(1, Arg.Any<int>()).Returns(100);
var x = calc.Add(1, -4);  // <-- 100
var y = calc.Add(1, 100); // <-- 100

// Ignoring all arguments
calc.Add(0, 0).ReturnsForAnyArgs(100);

// Accessing arguments and using lambda in return
calc.Add(0, 0).ReturnsForAnyArgs(args =>
    { return args[0]; }); // <-- returns the first arg

// Constraints on arguments
calc.Add(1, Arg.Is<int>(a => x < 100)).Returns(123);
var x = calc.Add(1, 50);  // <-- 123
var y = calc.Add(1, 300); // <-- 0 (default(int))

// Multiple return values
calc.Add(0, 0).ReturnsForAnyArgs(100, 200);
var x = calc.Add(10, 20); // <-- 100
var y = calc.Add(10, 20); // <-- 200
var z = calc.Add(10, 20); // <-- 200 (uses most recent)

// Was a method called?
calc.Received().Add(5, 10); // <-- expects exact args
calc.Received().Subtract(Arg.Any<int>(), Arg.Any<int>());

// Was a method called 4 times?
calc.Received(4).Subtract(Arg.Any<int>(), Arg.Any<int>());

// Was a method not called?
calc.DidNotReceive().RebootCalculator();

// Were methods called in a certain order?
Received.InOrder(() =>
    {
        calc.Add(1, 4);
        calc.Subtract(4, 8);
    });

// Throwing exception from a non-void method
calc.Add(0, 0).ReturnsForAnyArgs(args => { throw new Exception(); });

// Throwing exception from a void method
calc.When(c => c.ChangeMode(Arg.Any<string>())
    .Do(args => { throw new Exception(); });

// Raising EventArg events
calc.ModeChanged += Raise.Event();

// Raising custom events
calc.ErrorOccurred += Raise.EventWith(new object(), 
    new ErrorArgs("Divide by zero"));

// Doing something else after a non-void call
calc.Add(0, 0).ReturnsForAnyArgs(100)
    .AndDoes(args => someTestHelper());

// Doing something else after a void call
calc.When(c => c.ChangeMode(Arg.Any<string>())
    .Do(args => someTestHelper());