31
Introduction to NSubstitute

Introduction to nsubstitute

Embed Size (px)

Citation preview

Page 1: Introduction to nsubstitute

Introduction to

NSubstitute

Page 2: Introduction to nsubstitute

Agenda

Test double and its Types

Test Driven Development

Introduction to NSubstitute

Getting started

Creating a substitute & Setting a return value

Return for specific args, return for any args & return from a

function

Multiple return values

Replacing return values

Checking received calls & Clearing received calls

Argument matchers

Void calls and When..Do

Page 3: Introduction to nsubstitute

Cont..

Throwing exceptions

Raising events

Auto and recursive mocks

Setting out and ref args

Checking call order

Partial subs

Advantage of NSubstitute

Alternatives

Q & A

Page 4: Introduction to nsubstitute

Test double and its types

“Test Double” is a generic term for any case where you replace

a production object for testing purposes.

Test double types as listed below,

◦ Stub (used for providing the tested code with "indirect input")

◦ Mock (used for verifying "indirect output" of the tested code,

by first defining the expectations before the tested code is

executed)

◦ Spies (Are stubs that also record some information based on

how they were called. One form of this might be an email

service that records how many messages it was sent)

◦ Fake object (used as a simpler implementation, e.g. using an

in-memory database in the tests instead of doing real

database access)

◦ Dummy object (used when a parameter is needed for the

tested method but without actually needing to use the

parameter)

Page 5: Introduction to nsubstitute

Test Driven Development

TDD is an evolutionary approach to software development,

where you write a test cases first then you write just enough

production code to fulfill that test cases to pass and then

refactor the code.

The result of using this practice is a comprehensive suite of

unit tests that can be run at any time to provide feedback that

the software is still working.

Page 6: Introduction to nsubstitute

Introduction to NSubstitute

NSubstitute is a friendly substitute for .NET mocking

frameworks. It has a simple, succinct syntax to help

developers write clearer tests.

NSubstitute is designed for Arrange-Act-Assert (AAA) testing

and with Test Driven Development (TDD) in mind.

It’s a open source framework.

Latest version is 1.8.1.

Page 7: Introduction to nsubstitute

Getting started

The easiest way to get started is to reference NSubstitute

from your test project using the Nsubstitute NuGet package

via NuGet.

Alternatively you can download NSubstitute and add a

reference to the NSubstitute.dll file included in the download

into your test project.

Page 8: Introduction to nsubstitute

Creating a substitute & Setting a

return value The basic syntax for creating a substitute is:

var logSub = Substitute.For<ISomeInterface>();

Warning: Substituting for classes can have some nasty side-

effects. For starters, NSubstitute can only work with virtual

members of the class, so any non-virtual code in the class will

actually execute! If possible, stick to substituting interfaces.

Return value can be set for a method or property.

var calculator = Substitute.For<ICalculator>();

calculator.Add(1, 2).Returns(3);

calculator.Mode.Returns("DEC");

Page 9: Introduction to nsubstitute

Return for specific args, Return

for any args & Return from a

function Return for specific args

Return values can be configured for different combinations of

arguments passed to calls using argument matchers.

//Return when first arg is anything and second arg is 5:

calculator.Add(Arg.Any<int>(), 5).Returns(10);

Assert.AreEqual(10, calculator.Add(123, 5));

Page 10: Introduction to nsubstitute

Return for any args

A call can be configured to return a value regardless of the

arguments passed using the ReturnsForAnyArgs() extension

method.

//Return 100 regardless for the argument passed:

calculator.Add(1, 2).ReturnsForAnyArgs(100);

Assert.AreEqual(calculator.Add(1, 2), 100);

Assert.AreEqual(calculator.Add(-7, 15), 100);

Page 11: Introduction to nsubstitute

Return from a function

The return value for a call to a property or method can be set to

the result of a function. This allows more complex logic to be put

into the substitute. Although this is normally a bad practice, there

are some situations in which it is useful.

public interface IFoo {

string Bar(int a, string b);

}

var foo = Substitute.For<IFoo>();

foo.Bar(0, "").ReturnsForAnyArgs(x => "Hello " + x.Arg<string>());

Assert.That(foo.Bar(1, "World"), Is.EqualTo("Hello World"));

Page 12: Introduction to nsubstitute

Multiple return values

A call can also be configured to return a different value over

multiple calls.

var calculator = Substitute.For<ICalculator>();

calculator.Mode.Returns("DEC", "HEX", "BIN");

Assert.AreEqual("DEC", calculator.Mode);

Assert.AreEqual("HEX", calculator.Mode);

Assert.AreEqual("BIN", calculator.Mode);

Page 13: Introduction to nsubstitute

Replacing return values

The return value for a method or property can be set as many

times as required. Only the most recently set value will be

returned.

calculator.Mode.Returns("DEC,HEX,OCT");

calculator.Mode.Returns(x => "???");

calculator.Mode.Returns("HEX");

calculator.Mode.Returns("BIN");

Assert.AreEqual(calculator.Mode, "BIN");

Page 14: Introduction to nsubstitute

Checking received calls &

Clearing received calls Checking received calls

In some cases (particularly for void methods) it is useful to check that a specific call has been received by a substitute. This can be checked using the Received() extension method, followed by the call being checked. There is DidNotReceive() method to check if call is not recieved.

public class SomethingThatNeedsACommand

{

ICommand command;

public SomethingThatNeedsACommand(ICommand command) {

this.command = command; }

public void DoSomething() { command.Execute(); }

public void DontDoAnything() { }

}

Page 15: Introduction to nsubstitute

[TestMethod]

public void Should_execute_command()

{

//Arrange

var command = Substitute.For<ICommand>();

var something = newSomethingThatNeedsACommand(command);

//Act

something.DoSomething();

//Assert

command.Received().Execute();

}

Clearing received calls

A substitute can forget all the calls previously made to it using

the ClearReceivedCalls() extension method.

Page 16: Introduction to nsubstitute

Argument matchers

Argument matchers can be used when setting return values

and when checking received calls. They provide a way to

specify a call or group of calls, so that a return value can be

set for all matching calls, or to check a matching call has

been receieved.

Ignoring arguments

calculator.Add(Arg.Any<int>(), 5).Returns(7);

Conditionally matching an argument

calculator.Received().Add(1, Arg.Is<int>(x => x < 0));

Matching a specific argument

calculator.Add(0, 42);

Page 17: Introduction to nsubstitute

Void calls and When..Do Returns() can be used to get callbacks for members that return a

value, but for void members we need a different technique, because we can’t call a method on a void return. For these cases we can use the When..Do syntax.

public interface IFoo

{

void SayHello(string to);

}

[TestMethod]

public void SayHello()

{

var counter = 0;

var foo = Substitute.For<IFoo>();

foo.When(x => x.SayHello("World"))

.Do(x => counter++);

foo.SayHello("World");

foo.SayHello("World");

Assert.AreEqual(2, counter);

}

Page 18: Introduction to nsubstitute

Throwing exceptions

Callbacks can be used to throw exceptions when a member is called.

var calculator = Substitute.For<ICalculator>();

//For non-voids:

calculator.Add(-1, -1).Returns(x => { throw new Exception(); });

//For voids and non-voids:

calculator.When(x => x.Add(-2, -2))

.Do(x => { throw new Exception(); });

//Both calls will now throw.

Assert.Throws<Exception>(() => calculator.Add(-1, -1));

Assert.Throws<Exception>(() => calculator.Add(-2, -2));

Page 19: Introduction to nsubstitute

Raising events Events are “interesting” creatures in the .NET world, as you

can’t pass around references to them like you can with other members.

Instead, you can only add or remove handlers to events

public interface IEngine

{

event EventHandler Idling;

event EventHandler<LowFuelWarningEventArgs> LowFuelWarning;

event Action<int> RevvedAt;

}

public class LowFuelWarningEventArgs : EventArgs

{

public int PercentLeft { get; private set; }

public LowFuelWarningEventArgs(int percentLeft)

{

PercentLeft = percentLeft;

}

}

Page 20: Introduction to nsubstitute

Cont..int numberOfEvents,revvedAt = 0;

var engine = Substitute.For<IEngine>();

engine.LowFuelWarning += (sender, args) => numberOfEvents++;

//Raise event with specific args, any sender:

engine.LowFuelWarning += Raise.EventWith(newLowFuelWarningEventArgs(10));

//Raise event with specific args and sender:

engine.LowFuelWarning += Raise.EventWith(new object(), newLowFuelWarningEventArgs(10));

engine.RevvedAt += rpm => revvedAt = rpm;

engine.RevvedAt += Raise.Event<Action<int>>(123);

Assert.AreEqual(2, numberOfEvents);

Assert.AreEqual(123, revvedAt);

Page 21: Introduction to nsubstitute

Auto and recursive mocks Any properties or methods that return an interface, delegate,

or purely virtual class* will automatically return substitutes themselves. This is commonly referred to as recursive mocking.

It is useful because you can avoid explicitly creating each substitute, which means less code.

public interface IContext

{

IRequest CurrentRequest { get; }

}

public interface IRequest

{

IIdentity Identity { get; }

IIdentity NewIdentity(string name);

}

public interface IIdentity

{

string Name { get; }

string[] Roles();

}

Page 22: Introduction to nsubstitute

var context = Substitute.For<IContext>();

context.CurrentRequest.Identity.Name.Returns("My pet fish Eric");

Assert.AreEqual("My pet fish Eric",

context.CurrentRequest.Identity.Name);

Here CurrentRequest is automatically going to return a

substitute for IRequest, and the IRequest substitute will

automatically return a substitute for IIdentity.

Cont..

Page 23: Introduction to nsubstitute

Setting out and ref args

Out and ref arguments can be set using a Returns() callback,

or using When..Do.

public interface ILookup

{

bool TryLookup(string key, out string value);

}

For the interface above we can configure the return value and

set the output of the second argument

Page 24: Introduction to nsubstitute

//Arrange

var value = "";

var lookup = Substitute.For<ILookup>();

lookup

.TryLookup("hello", out value)

.Returns(x =>

{

x[1] = "world!";

return true;

});

//Act

var result = lookup.TryLookup("hello", out value);

//Assert

Assert.True(result);

Assert.AreEqual(value, "world!");

Cont..

Page 25: Introduction to nsubstitute

Checking call order Sometimes calls need to be made in a specific order.

Depending on the timing of calls like this is known as temporal coupling.

Ideally we’d change our design to remove this coupling, but for times when we can’t NSubstitute lets us resort to asserting the order of calls.

[Test]

public void TestCommandRunWhileConnectionIsOpen()

{

var connection = Substitute.For<IConnection>();

var command = Substitute.For<ICommand>();

var subject = new Controller(connection, command);

subject.DoStuff();

Received.InOrder(() =>{

connection.Open();

command.Run(connection);

connection.Close();});

}

Page 26: Introduction to nsubstitute

Partial Stubs

Partial substitutes allow us to create an object that acts like a real instance of a class, and selectively substitute for specific parts of that object.

This is useful for when we need a substitute to have real behavior except for a single method that we want to replace, or when we just want to spy on what calls are being made.

public class SummingReader

{

public virtual int Read(string path)

{

var s = ReadFile(path);

return s.Split(',').Select(int.Parse).Sum();

}

public virtual string ReadFile(string path) { return "the result of reading the file here"; }

}

Page 27: Introduction to nsubstitute

Cont…[Test]

public void ShouldSumAllNumbersInFile()

{

var reader = Substitute.ForPartsOf<SummingReader>();

reader.ReadFile(Arg.Is("foo.txt")).Returns("1,2,3,4,5");

var result = reader.Read("foo.txt");

Assert.That(result, Is.EqualTo(15));

}

Page 28: Introduction to nsubstitute

Advantages of NSubstitute

It works really well with TDD.

You do not have to pay the ‘lambda tax’ which is present in

RhinoMocks, Moq and Fake it easy.

NSubstitute uses c# language features in a really clever way

to simplify usage.

◦ This results in each interaction requiring less code and

being easier to read.

Page 29: Introduction to nsubstitute

Alternatives

Moq

JustMock

FakeItEasy

EasyMock.NET

NMock 3

Rhino Mocks

Page 30: Introduction to nsubstitute

Q & A

Page 31: Introduction to nsubstitute

Thank you