Search code examples
c#unit-testingmockingnsubstitutespy

Overriding protected abstract method in NSubstitute


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();
}

Solution

  • 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).