NSubstitute ForPartsOf...当调用真正的方法时

NSubstitute ForPartsOf...When calls real method

我正在尝试使用 ForPartsOf<...>() 替代方法,然后使用 subst.Configure().MyMethod(...).Returns(...)subst.When(x => x.MyMethod(..)).Returns(...),但在这两种情况下都会调用真正的 MyMethod。我的印象是 Configure()When() 都应该确保 MyMethod() 调用是在 "configure mode" 中进行的,因此不会进行真正的调用。我错了吗?还是我做错了什么?

下面是我的(大大简化并更改了名称的)代码。对于 subst1subst2,真正的 NeedsMoreWork 方法是用 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);
}

使用 NSubstitute 时,要成为 mocked/stubbed 的成员需要成为 virtual 才能被覆盖。

引用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 现在可以根据需要存根

[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

注意:强调我的