I want to create a spy using NSubstitute, but can't find out how after a lot of googling and reading the docs.
The idea would be to have the substitute with an overriden method that I could use to monitor received calls, etc. as usual with mocking. But how to achieve this with NSubstitute?
I would prefer not to relax neither protected
nor abstract
modifiers.
Here's what I would like to do:
[Test]
public void Items_Add_shouldCall_ItemAddedMethod()
{
var sut = Substitute.For<ItemsRepository>();
// how to configure???
sut.Items.Add(Substitute.For<IItem>());
sut.Received().ItemAdded(); // currently cannot do this
// ...
public abstract class ItemsRepository
{
public ObservableCollection<IItem> Items { get; set; }
= new ObservableCollection<IItem>();
// ...
protected abstract void ItemAdded();
}
The NSubstitute API relies on calling a method to configure it with Returns
or check Received()
calls, which means for standard use we can only work with calls that can be invoked via the public API (which excludes protected
and private
members). There is no problem with the abstract
part of your question; it is fine to use NSubstitute with accessible abstract
members.
Even though C#, by design, prevents us from interacting directly with protected
members, NSubstitute still records calls made to these members. We can check these by using the "unofficial" .ReceivedCalls()
extension:
public abstract class ItemsRepository {
public virtual void Add(int i) { ItemAdded(); }
protected abstract void ItemAdded();
}
public class Fixture {
[Fact]
public void CheckProtectedCall() {
var sub = Substitute.For<ItemsRepository>();
sub.When(x => x.Add(Arg.Any<int>())).CallBase();
sub.Add(42);
var called = sub.ReceivedCalls().Select(x => x.GetMethodInfo().Name);
Assert.Contains("ItemAdded", called);
}
}
Other options include creating a derived class (as @Nkosi pointed out), or making the member internal
instead of protected
and using InternalsVisibleTo
to make the member accessible to your test code (if you do the latter I recommend installing NSubstitute.Analyzer to help ensure this is configured correctly).