AutoFixture + NSubstitute + 虚拟方法

AutoFixture + NSubstitute + Virtual methods

想知道是否有办法使用 AutoFixture 和 NSubstitute 在具体 class 上模拟虚拟方法。我已经能够使用最小起订量轻松完成此操作,如下所示:

public class SomeConcreteClass
{
    public string MethodA()
    {
        return MethodB();
    }

    public virtual string MethodB()
    {
        return "AAA";
    }
}

[TestFixture]
public class SomeConcreteClassTests
{
    private IFixture _fixture;
    private SomeConcreteClass _someConcreteClass;

    [SetUp]
    protected void Setup()
    {
        _fixture = new Fixture()
            .Customize(new AutoMoqCustomization());
        var someConcreteClassMock = _fixture.Create<Mock<SomeConcreteClass>>();
        _someConcreteClass = someConcreteClassMock.Object;
        someConcreteClassMock.CallBase = true;
    }

    [Test]
    public void SomeScenario()
    {
        Mock.Get(_someConcreteClass).Setup(m => m.MethodB()).Returns("BBB");
        var actual = _someConcreteClass.MethodA();
        actual.ShouldBe("BBB");
    }
}

如果您使用 AutoFixture 对 Parametrised Tests, here illustrated using xUnit.net 的支持(但是,IIRC,对 NUnit 也有类似的支持),这是最好的实现方式:

[Theory, AutoNSubstituteData]
public void ImplicitSubtituteViaAttribute([Substitute]SomeConcreteClass scc)
{
    scc.MethodB().Returns("BBB");
    var actual = scc.MethodB();
    Assert.Equal("BBB", actual);
}

使用 [Substitute] 属性可以让您明确地告诉 AutoFixture,尽管您要求一个具体的 class,它应该通过 NSubstitute 创建它,以便您可以覆盖它可能拥有的任何虚拟成员.

AutoNSubstituteData 定义如下:

public class AutoNSubstituteDataAttribute : AutoDataAttribute
{
    public AutoNSubstituteDataAttribute() :
        base(() => new Fixture().Customize(new AutoNSubstituteCustomization()))
    {
    }
}

AutoDataAttribute 来自 AutoFixture.Xunit2, but if you prefer NUnit over xUnit.net, you should be able to use AutoFixture.NUnit3


否则,我不确定您能否获得与 AutoFixture.AutoMoq 完全相同的结果。在这个退化的例子中,你可以这样做:

[Fact]
public void ImperativeWorkaround()
{
    var fixture = new Fixture().Customize(new AutoNSubstituteCustomization());
    fixture.Register(() => Substitute.For<SomeConcreteClass>());
    var scc = fixture.Create<SomeConcreteClass>();
    scc.MethodB().Returns("BBB");

    var actual = scc.MethodB();

    Assert.Equal("BBB", actual);
}

然而,这是毫无意义的,因为您也可以这样写:

[Fact]
public void Reduction()
{
    var scc = Substitute.For<SomeConcreteClass>();
    scc.MethodB().Returns("BBB");

    var actual = scc.MethodB();

    Assert.Equal("BBB", actual);
}

换句话说,AutoFixture 实际上并没有任何解决方法。

我可以想象真正的问题是在实际使用中,所讨论的具体 class 有您希望用数据填充的其他成员或构造函数数据。问题是由于 NSubstitute 的设计方式,我不知道有什么方法可以声明性地请求 'substitute';您将不得不使用 Substitute.For 方法,然后该方法会完全短路 AutoFixture 挂接到进程并添加其自身行为的能力。

对于 Moq,这是可能的,因为在 OP 中,您不是向 AutoFixture 询问 SomeConcreteClass 对象,而是向 Mock<SomeConcreteClass> 询问,这使得 AutoFixture 能够区分。

换句话说,Moq 遵循 Zen of Python,即 显式优于隐式, 这使得它的可扩展性达到了使用 NSubstitute 不容易实现的程度。出于这个原因,我一直认为最小起订量更好 API.