NSubstitute Tutorial
posted in dotnet on • by Wouter Van SchandevijlThe creators of NSubstitute craved a mocking framework with comparable capabilities to the alternatives but with a shorter, more succinct syntax. They have not failed to do so and I loved the NSubstitute syntax right away.
The examples assume
// Packages:
Install-Package NSubstitute
Install-Package NUnit
// Interface:
interface ICalculator
{
int Add(int a, int b);
int Divide(int n, int divisor, out float remainder);
string Mode { get; set; }
void SetMode(string mode);
}
Creating a substitute
// For an interface
ICalculator nsub = Substitute.For<ICalculator>();
// For a class
Calculator nsub = Substitute.For<Calculator>(ctorArg1, ctorArg2);
When substituting a class, only virtual/abstract members can be mocked.
There are generic overloads for up to three different types. When
providing multiple types, max one class can be used. For more than three:
var sub = Substitute.For(
new[] { typeof(IComparable), typeof(IDisposable), typeof(ICloneable), typeof(Calculator) },
new object[] { /* ctor arguments for Calculator */ }
);
Assert.IsInstanceOf<IDisposable>(sub);
Assert.IsInstanceOf<Calculator>(sub);
Matching arguments
// Arg.Any and fixed value
nsub.Add(Arg.Any<int>(), 5).Returns(10);
Assert.That(nsub.Add(999, 5), Is.EqualTo(10));
// Arg.Is: Fixed value and Predicate
nsub.Add(Arg.Is(1), Arg.Is<int>(x => x < 0)).Returns(5);
Assert.That(nsub.Add(1, -2), Is.EqualTo(5));
// First matcher still applies
Assert.That(nsub.Add(998, 5), Is.EqualTo(10));
// ReturnsForAnyArgs overwrites everything
nsub.Add(0, 0).ReturnsForAnyArgs(100);
Assert.That(nsub.Add(1, -2), Is.EqualTo(100));
Verification
Did receive calls:
// Methods
nsub.Add(0, 1);
nsub.Received().Add(0, 1);
nsub.ReceivedWithAnyArgs().Add(0, 0);
// Property Getter
string call = nsub.Mode;
string result = nsub.Received().Mode;
// Property Setter
nsub.Mode = "BIN";
nsub.Received().Mode = "BIN";
// Reset calls
nsub.ClearReceivedCalls();
// Verify with predicate
nsub.Add(0, 1);
nsub.Add(0, 2);
nsub.Add(0, 3);
nsub.Received(2).Add(Arg.Is(0), Arg.Is<int>(x => x <= 2));
// In this case the Arg.Is(0) is required by NSubstitute
// (providing 0 directly results in an AmbiguousArgumentsException)
Did not receive calls:
nsub.DidNotReceive().Add(Arg.Any<int>(), 9);
// The actual arguments to Add are simply ignored with DidNotReceiveWithAnyArgs
nsub.DidNotReceiveWithAnyArgs().Add(0, 0);
Providing values
Without setup the following is returned:
- For primitives:
default(int)
- For strings:
string.Empty
- For IEnumerables:
Enumerable.Empty<T>()
- For interfaces: a
Substitute.For<T>()
, the same reference when called with the same arguments
When substituting a class, non-virtual methods returning an interface
cannot be mocked and will return null
.
Methods and properties
// Methods
nsub.Add(1, 3).Returns(4);
Assert.That(nsub.Add(1, 3), Is.EqualTo(4));
Assert.That(nsub.Add(1, 3), Is.EqualTo(4));
// Properties
nsub.Mode = "HEX";
Assert.That(nsub.Mode, Is.EqualTo("HEX"));
// Can also use the same syntax as for methods:
nsub.Mode.Returns("DEC");
Assert.That(nsub.Mode, Is.EqualTo("DEC"));
// Provide multiple return values
nsub.Mode.Returns("DEC", "HEX", "BIN");
Assert.AreEqual("DEC", nsub.Mode);
Assert.AreEqual("HEX", nsub.Mode);
Assert.AreEqual("BIN", nsub.Mode);
// Multiple return values with a function
nsub.Mode.Returns(
x => "DEC",
x => "HEX",
x => throw new Exception()
);
Assert.AreEqual("DEC", nsub.Mode);
Assert.AreEqual("HEX", nsub.Mode);
Assert.Throws<Exception>(() => { var result = nsub.Mode; });
For all of type
using NSubstitute.Extensions;
nsub.Add(1, 3).Returns(3);
nsub.ReturnsForAll<int>(5);
Assert.That(nsub.Add(1, 3), Is.EqualTo(3));
Assert.That(nsub.Add(0, 9), Is.EqualTo(5));
Assert.That(nsub.Divide(0, 9, out float remainder), Is.EqualTo(5));
Out and ref
ref
works in the same way as out
.
nsub.Divide(12, 5, out float remainder).Returns((CallInfo callInfo) => {
callInfo[2] = 0.4F; // [2] = 3th parameter = remainder
return 2;
});
Assert.AreEqual(2, nsub.Divide(12, 5, out remainder));
Assert.AreEqual(0.4F, remainder);
Exceptions
using NSubstitute.ExceptionExtensions;
nsub.Add(1, 1).Throws(new Exception());
Assert.Throws<Exception>(() => nsub.Add(1, 1));
nsub.When(x => x.SetMode("HEX")).Throw<Exception>();
Assert.Throws<Exception>(() => nsub.SetMode("HEX"));
Side effect callbacks
AndDoes:
Do something after Add
was called.
int counter = 0;
nsub
.Add(0, 0)
.ReturnsForAnyArgs(x => 0)
.AndDoes(x => counter++);
nsub.Add(7, 3);
nsub.Add(2, 2);
Assert.AreEqual(counter, 2);
Make voids do something:
bool called = false;
sub
.When(x => x.SetMode("HEX"))
.Do(x => called = true);
sub.SetMode("HEX");
Assert.True(called);
Do something with arguments passed:
int argumentUsed = 0;
nsub.Add(10, Arg.Do<int>(x => argumentUsed = x));
nsub.Add(10, 42);
nsub.Add(11, 0); // does not overwrite argumentUsed because first arg is not 10
Assert.AreEqual(42, argumentUsed);
Less frequently used
Partial Class Substitution
public class SummingReader
{
/// <summary>
/// We will actually execute this
/// </summary>
public virtual int CalculateSum(string path)
{
var s = ReadFile(path);
return s.Split(',').Select(int.Parse).Sum();
}
/// <summary>
/// While substituting this behavior
/// </summary>
public virtual string ReadFile(string path)
{
throw new Exception($"Actually attempted to access '{path}' on filesystem!");
}
}
// Tests:
var reader = Substitute.ForPartsOf<SummingReader>();
// Without Arg. matchers, the actual ReadFile will be executed
Assert.Throws<Exception>(() => reader.ReadFile("foo.txt").Returns("1,2,3"));
// The Arg.Is makes sure that ReadFile is not executed
reader.ReadFile(Arg.Is("foo.txt")).Returns("1,2,3");
int result = reader.CalculateSum("foo.txt");
Assert.That(result, Is.EqualTo(6));
// Alternatively: Use DoNotCallBase to play it (a bit) safer.
// Make sure the ReadFile call won't call real implementation
reader = Substitute.ForPartsOf<SummingReader>();
reader.When(x => x.ReadFile("foo.txt")).DoNotCallBase(); // <-- Magic here
reader.ReadFile("foo.txt").Returns("1,2,3");
result = reader.CalculateSum("foo.txt");
Assert.That(result, Is.EqualTo(6));
Provide values using CallInfo callback
nsub
.Add(Arg.Any<int>(), Arg.Any<int>())
.Returns((CallInfo callInfo) =>
{
// Get argument value with indexer
int firstArg = (int)callInfo[0];
// By position
int secondArg = callInfo.ArgAt<int>(1);
// By type
// Throws because there should be a Single parameter of the type
Assert.Throws<AmbiguousArgumentsException>(() => callInfo.Arg<int>());
// Get parameter type information
Assert.That(callInfo.ArgTypes().First(), Is.EqualTo(typeof(int)));
// Actual return value
return firstArg + secondArg;
});
Assert.That(nsub.Add(1, 3), Is.EqualTo(4));
Events
Summary
So I like NSubstitute. Like a lot. That’s it.
For more examples - as UnitTests - check the Github source.