Wednesday, October 28, 2015

NSubstitute

One of the key benefits of unit testing is that it pushes you to code to abstractions. Each class has a clear inside and outside, and your tests document the inside bits. The outside stays wrapped in an interface or abstract class so we can change its behavior in tests. When you go down this path, having a tool to make fake versions of interfaces in a simple and painless way is key, which is why I recommend NSubstitute. It is elegantly simple, very flexible, and makes it hard to get into trouble. In this post I will go over some fundamentals of NSubstitute, and compare them to how things work in the more well known Moq library.


A Sample Interface

In this article, I'm going to make use of an interface ISample, and two simple classes AllVirtual and NonVirtual

public interface ISample
{
  DateTime GetCurrentDateTime();
  
  int GetCurrentCount();

  string SomeString { get; set; }

  IEnumerable<string> Names { get; }

  ISample SingleChild { get; set; }

  IEnumerable<ISample> GetChildren();

  AllVirtual GetAllVirtual();

  NonVirtual GetNonVirtual();
}

public class AllVirtual
{
  public virtual string Name { get; set; }
}

public class NonVirtual
{
  public string Name { get; set; }
}

A few things to point out: this interface has a mixture of properties and methods, value and reference types, single values and enumerations. By and large it sticks to returning interfaces (avoiding a "leaky abstractions"), but I have a couple of direct class references, GetAllVirtual and GetNonVirtual, which I'll come back to in a bit. Finally, the interface is public. NSubstitute requires that out of the box, but there is a way around that too. Okay, now we have our interface. Let's get started faking it.

Making the Fake


With NSubstitute, this is a one line operation.

var fakeSample = Substitute.For<ISample>();

Contrast this with the two step operation with Moq:

Mock mock = new Mock<ISample>();
// do mock setup stuff
var fakeSample = (ISample)mock.Object;

I realize this is an aesthetic point, but I find the former much cleaner, especially the use of the generic call to remove the need for an explicit cast. Repeated for every interface in every test you write, this reduction in cruft makes a real difference.

Default Behaviors

Without any further setup, your fake ISample is set to do some basic things:

[Fact]
public void NSub_NoSPecialSetup_ReturnsRecursiveFakes()
{
  
  var fakeSample = Substitute.For<ISample>();

  Assert.Equal(DateTime.MinValue, fakeSample.GetCurrentDateTime());
  Assert.Equal(0, fakeSample.GetCurrentCount());
  Assert.Equal("", fakeSample.SomeString);
  Assert.NotNull(fakeSample.SingleChild);
  Assert.NotNull(fakeSample.SingleChild.SingleChild.SingleChild); // Recursive behavior.
  Assert.NotNull(fakeSample.Names);
  Assert.Equal(0, fakeSample.Names.Count());
  Assert.NotNull(fakeSample.GetChildren());
  Assert.Equal(0, fakeSample.GetChildren().Count());

  Assert.NotNull(fakeSample.GetAllVirtual());
  Assert.Null(fakeSample.GetNonVirtual());
}
As you can see, except for the last method, this object never returns null. For simple values, it returns logical defaults: empty strings, DateTime.MinValue, or zero. For IEnumerable it returns empty collections, and for interfaces and classes that have all virtual methods, it returns NSubstitute fakes. This allows you to do a lot with virtually no setup, leading to much more expressive tests, in which you only set up stuff you care about in that test.

The restriction to all virtual methods is useful. NSubstitute generates proxy subclasses of the objects it tests, and since non-virtual methods cannot be overridden, this prevents you from accidentally calling real code. You can always get around this by explicitly defining a return value for this property, presumably after inspecting its methods to make sure they don't reformat hard drives or launch missiles.

Moq will provide defaults for simple values (but not strings), but for classes and interfaces, you need to explicitly enable this behavior. And Moq does not make a distinction between all virtual classes and classes with non-overridable logic, as NSubstitute does.

[Fact]
public void Moq_WithDefaultValues_ReturnsRecursiveMocks()
{
  var mock = new Mock<ISample>{ DefaultValue = DefaultValue.Mock };  // DefaultValue setting allows recursive mocks.
  var fakeSample = (ISample)mock.Object;

  Assert.Equal(DateTime.MinValue, fakeSample.GetCurrentDateTime());
  Assert.Equal(0, fakeSample.GetCurrentCount());
  Assert.Null(fakeSample.SomeString);  // differs from NSubstitute.
  Assert.NotNull(fakeSample.SingleChild.SingleChild.SingleChild);
  Assert.NotNull(fakeSample.Names);
  Assert.Equal(0, fakeSample.Names.Count());
  Assert.NotNull(fakeSample.GetChildren());
  Assert.Equal(0, fakeSample.GetChildren().Count());

 Assert.NotNull(fakeSample.GetAllVirtual());
 Assert.NotNull(fakeSample.GetNonVirtual());  // differs from NSubstitute.
}

Scripting Output

Of course, sometimes you want to define the output the class will provide. This is done with the "Returns" extension method:

fakeSample.SomeString.Returns("hello");
fakeSample.GetCurrentDateTime().Returns(new DateTime(2016, 2, 29));
fakeSample.GetCurrentCount().Returns(-1);
fakeSample.Names.Returns(new [] {"Larry", "Curly", "Moe"});
The use of an extension method syntax allows you to script the test object directly, rather than have to work with an intermediate "Mock" object:

var mock = new Mock()  ;
mock.Setup(s => s.SomeString).Returns("test");
var fakeSample = (ISample)mock.Object;

Verifying Calls

You can verify calls on your substitutes with the "Received()" and "ReceivedWithAnyArgs() methods.

fakeSample.Received().Log("This happened.");  
fakeSample.Recieved().Log(Arg.Any);
fakeSample.Received().Log(Arg.Is(s => s.Contains("happened"));
The first option is most expressive, the second is the loosest (loose is good with fakes), and the third, using argument specific predicates, allows you to verify only the argument you care about. The ability to fine tune your verification down to a single argument, or single fact about an argument, supports the notion of having each test support a single logical concept. It is noteworthy that NSubstitute does not have a "VerifyAll()" method, which in Moq tests that a series of calls were made in a specific order, which leads to creating very brittle, unmaintainable tests. Sometimes omitting a feature is a good thing. This is the prerogative of a newwe framework.

Supporting the complex

NSubstitute gets you a lot with very little set up, but sometimes you want to create more complex scenarios, especially if you are reproducing a bug in a test. To give a flavor for the more esoteric things NSubstitute can do, let's create a fake that throws an exception on the third time it's called:

[Fact]
public void Fake_CalledThreeTimes_Throws()
{
  var fakeSample = Substitute.For<ISample>();
   
  int callCount = 0;
  fakeSample.When(x => x.GetChildren()).Do(info =>
  {
    callCount++;
    if (callCount == 3)
 {
   throw new Exception("Boom!");
 }
    });

    fakeSample.GetChildren();
    fakeSample.GetChildren();
    Assert.Throws(() => fakeSample.GetChildren());
}
The docs, which are exceptionally clear and well written, cover these features well.

Supporting Arrange/Act/Assert

By allowing you to work directly with the fake object for both scripting output and verifying input, NSubstitute lends itself to the arrange/act/assert pattern, where you set up a scenario, call the code under test, and then state, in simple, limited terms, the expected state of the system. This is much more expressive than stating expected results at the beginning of the test, and then uttering the magic invocation "Verify!". It's much more of a coherent narrative: this was the scenario, this happened, and this was the result. Simple tests are much more likely to be read, used, and maintained.

Because of its simplicity, I recommend using NSubstitute even on projects and on teams that use Moq. No need to rewrite old tests, but complex prior choices shouldn't hold back simpler options going forward.

Fake it till you make it

The real power of fake-based testing is that your development starts to follow a mini-kanban approach, where you limit your work in progress to a simple piece of logic, and wrap complex stuff behind interfaces, which you can get to later. Once you have the current class working, you pull the next interface off the shelf, and build its implementation. This leads to producing code in a steady stream of robust small components.

Thanks to Roy Osherove, whose post The Future of Isolation Frameworks inspired my original interest in NSubstitute.


1 comment:

  1. Excellent article! Very well written and I like the detailed comparisons.

    The only complaint I have is that you don't include an example of how Moq utilizes the "VerifyAll()" method, but that's pretty easy to research.

    ReplyDelete