3

I'm trying to substitute a method using ForPartsOf<...>() and then subst.Configure().MyMethod(...).Returns(...) or subst.When(x => x.MyMethod(..)).Returns(...), but in both cases the real MyMethod gets called. I was under the impression that both Configure() and When() was supposed to make sure the MyMethod() call was made in "configure mode", so that no real call will be made. Am I wrong? Or am I doing something wrong?

Here below is a my (much simplified, and namechanged) code. For both subst1 and subst2, the real NeedsMoreWork method gets called with item == null.

public interface IMyClass
{
    bool NeedsMoreWork(Item item, out Part part);
    bool DoWork(Item item);
}

public class MyClass : IMyClass
{
    private ILogger log;

    public MyClass(ILogger log)
    {
        this.log = log;
    }

    public bool NeedsMoreWork(Item item, out Part part)
    {
        log.Debug($"Examining item {item.Id}");
        part = null;
        if (item.Completed())
        {
            log.Debug($"Item {item.Id} already completed.");
            return false;
        }
        part = item.GetNextPart();
        log.Debug($"Item {item.Id} needs work on part {part.Id}.");
        return true;            
    }

    public bool DoWork(Item item)
    {
        if (!item.NeedsMoreWork(item, out Part part))
            return false;
        log.Debug($"Starting work on part {part.Id}.");
        // Do work on part.
        log.Debug($"Work completed on part {part.Id}.");
        return true;
    }
}

[TestMethod]
public void TestDoWork()
{
    // Version with Configure():
    var subst1 = Substitute.ForPartsOf<MyClass>(Substitute.For<ILogger>());
    subst1.Configure()
        .NeedsMoreWork(Arg.Any<Item>(), out Arg.Any<Part>())
        .Returns(false);

    // Version with WhenForAnyArgs():
    var subst2 = Substitute.ForPartsOf<MyClass>(Substitute.For<ILogger>());
    subst2.WhenForAnyArgs(x => x.NeedsMoreWork(Arg.Any<Item>(), out Arg.Any<Part>())
        .Returns(false);
}
Kjell Rilbe
  • 1,331
  • 14
  • 39
  • 2
    members to be overridden need to be `virtual` in order to be mocked/stubbed. https://nsubstitute.github.io/help/partial-subs/ – Nkosi Sep 18 '19 at 16:21

1 Answers1

8

When using NSubstitute, members to be mocked/stubbed need to be virtual in order to be overridden.

Reference Partial subs and test spies

public class MyClass : IMyClass {
    private readonly ILogger log;

    public MyClass(ILogger log) {
        this.log = log;
    }

    public virtual bool NeedsMoreWork(Item item, out Part part) { //<-- THIS IS NOW VIRTUAL
        log.Debug($"Examining item {item.Id}");
        part = null;
        if (item.Completed()) {
            log.Debug($"Item {item.Id} already completed.");
            return false;
        }
        part = item.GetNextPart();
        log.Debug($"Item {item.Id} needs work on part {part.Id}.");
        return true;            
    }

    public bool DoWork(Item item) {
        if (!NeedsMoreWork(item, out Part part))
            return false;
        log.Debug($"Starting work on part {part.Id}.");
        // Do work on part.
        log.Debug($"Work completed on part {part.Id}.");
        return true;
    }
}

NeedsMoreWork can now be stubbed as needed

[TestMethod]
public void TestDoWork_Should_Return_False() {
    //Arrange
    var subject = Substitute.ForPartsOf<MyClass>(Substitute.For<ILogger>());
    bool expected = false;
    subject.Configure().NeedsMoreWork(Arg.Any<Item>(), out Arg.Any<Part>()).Returns(expected);

    //Act
    bool actual = subject.DoWork(new Item());

    //Assert - FluentAssertions
    actual.Should().Be(expected);
}

Note the CAUTION comment. If we had not used Configure() here before .NeedsMoreWork(...) then the real method would have executed before we had a chance to override the behavior. In some cases this may not be a problem, but if in doubt make sure you call Configure() first so NSubstitute knows you are configuring a call and don’t want to run any real code. (This still does not guarantee real code will not run – remember, NSubstitute will not prevent non-virtual calls from executing.)

The Configure() method is only available in NSubstitute 4.0 and above. For verisons prior to 4.0 we need to use When .. DoNotCallBase

note: emphasis mine

Community
  • 1
  • 1
Nkosi
  • 235,767
  • 35
  • 427
  • 472
  • I'll try tomorrow. Just wanted to ask: I thought it was enough that it implements an interface method. Does it in fact *also* need to be declared virtual? – Kjell Rilbe Sep 18 '19 at 19:47
  • 3
    Interfaces are by default overridable. You were not mocking the interface. If mocking a class the members to mock have to be abstract or virtual. – Nkosi Sep 18 '19 at 22:14
  • Understood, finally. :-) Thanks! – Kjell Rilbe Sep 19 '19 at 05:56