A Sample Interface
In this article, I'm going to make use of an interfaceISample
, 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.
Excellent article! Very well written and I like the detailed comparisons.
ReplyDeleteThe 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.