当我在单元测试中直接使用 Moq 来模拟IBuilderFactory并实例化BuilderService时,我可以通过测试,以验证Create()IBuilderFactory方法被正确调用了一次。

但是,当我将 Autofixture AutoMoqCustomization 一起使用时,卡住了IBuilderFactory的模拟并使用BuilderService实例化了fixture.Create<BuilderService>,则出现以下异常:



如果我将CubeBuilder密封(用SealedCubeBuilder调用的密封类IBuilderFactoryForSealedBuilder.Create()代替表示),则测试将通过AutoFixture与AutoMoqCustomization一起通过,并且不会引发异常。

我想念什么吗?由于我直接通过Moq通过了测试,因此我相信这与Autofixture和/或AutoMoqCustomization有关。这是期望的行为吗?如果是这样,为什么?

要重现,我正在使用:

using Moq;
using Ploeh.AutoFixture;
using Ploeh.AutoFixture.AutoMoq;
using Xunit;

这是说明行为的四个测试:

public class BuilderServiceTests {
    [Fact]
    public void CubeBuilderFactoryCreateMethodShouldBeCalled_UsingMoq() {
        var factory = new Mock<IBuilderFactory>();
        var sut = new BuilderService(factory.Object);
        sut.Create();
        factory.Verify(f => f.Create(), Times.Once());
    }
    [Fact]
    public void CubeBuilderFactoryCreateMethodShouldBeCalled_UsingAutoFixture() {
        var fixture = new Fixture().Customize(new AutoMoqCustomization());
        var factory = fixture.Freeze<Mock<IBuilderFactory>>();
        var sut = fixture.Create<BuilderService>();
        sut.Create(); // EXCEPTION THROWN!!
        factory.Verify(f => f.Create(), Times.Once());
    }
    [Fact]
    public void SealedCubeBuilderFactoryCreateMethodShouldBeCalled_UsingMoq() {
        var factory = new Mock<IBuilderFactoryForSealedBuilder>();
        var sut = new BuilderServiceForSealedBuilder(factory.Object);
        sut.Create();
        factory.Verify(f => f.Create(), Times.Once());
    }
    [Fact]
    public void SealedCubeBuilderFactoryCreateMethodShouldBeCalled_UsingAutoFixture() {
        var fixture = new Fixture().Customize(new AutoMoqCustomization());
        var factory = fixture.Freeze<Mock<IBuilderFactoryForSealedBuilder>>();
        var sut = fixture.Create<BuilderServiceForSealedBuilder>();
        sut.Create();
        factory.Verify(f => f.Create(), Times.Once());
    }
}

以下是必需的类:

public interface IBuilderService { IBuilder Create(); }
public class BuilderService : IBuilderService {
    private readonly IBuilderFactory _factory;
    public BuilderService(IBuilderFactory factory) { _factory = factory; }
    public IBuilder Create() { return _factory.Create(); }
}
public class BuilderServiceForSealedBuilder : IBuilderService {
    private readonly IBuilderFactoryForSealedBuilder _factory;
    public BuilderServiceForSealedBuilder(IBuilderFactoryForSealedBuilder factory) { _factory = factory; }
    public IBuilder Create() { return _factory.Create(); }
}

public interface IBuilderFactoryForSealedBuilder { SealedCubeBuilder Create(); }
public interface IBuilderFactory { CubeBuilder Create(); }
public interface IBuilder { void Build(); }

public abstract class Builder : IBuilder {
    public void Build() { } // build stuff
}

public class CubeBuilder : Builder {
    private Cube _cube;
    public CubeBuilder(Cube cube) { _cube = cube; }
}

public sealed class SealedCubeBuilder : Builder {
    private Cube _cube;
    public SealedCubeBuilder(Cube cube) { _cube = cube; }
}

public class Cube { }

最佳答案

如果查看堆栈跟踪,您会注意到异常发生在Moq的深处。 AutoFixture是一个有目的的库,它持有的一种意见是空值是无效的返回值。因此,AutoMoqCustomization像这样配置所有Mock实例:

mock.DefaultValue = DefaultValue.Mock;

(除其他事项外)。因此,您无需AutoFixture就可以完全重现失败的测试:
[Fact]
public void ReproWithoutAutoFixture()
{
    var factory = new Mock<IBuilderFactory>();
    factory.DefaultValue = DefaultValue.Mock;
    var sut = new BuilderService(factory.Object);
    sut.Create(); // EXCEPTION THROWN!!
    factory.Verify(f => f.Create(), Times.Once());
}

奇怪的是,它似乎仍然适用于密封类。但是,这不是很正确,而是起源于OP测试False Negatives

考虑一下Moq的Characterization Test:
[Fact]
public void MoqCharacterizationForUnsealedClass()
{
    var factory = new Mock<IBuilderFactory>();
    factory.DefaultValue = DefaultValue.Mock;
    Assert.Throws<ArgumentException>(() => factory.Object.Create());
}

Moq正确地引发了一个异常,因为它被要求创建一个CubeBuilder实例,并且它不知道该怎么做,因为CubeBuilder没有默认构造函数,并且没有Setup告诉它如何处理对Create的调用。

(顺便说一句,具有讽刺意味的是,AutoFixture完全可以创建CubeBuilder实例,但是Moq中没有可扩展点,可以使AutoFixture进入并接管Moq的默认对象实例创建行为。)

现在,当密封返回类型时,请考虑以下特性测试:
[Fact]
public void MoqCharacterizationForSealedClass()
{
    var factory = new Mock<IBuilderFactoryForSealedBuilder>();
    factory.DefaultValue = DefaultValue.Mock;
    var actual = factory.Object.Create();
    Assert.Null(actual);
}

事实证明,在这种情况下,尽管隐式告诉了Moq不要返回null,但Moq还是这样做。

我的理论是,实际上是在上面的MoqCharacterizationForUnsealedClass中,factory.DefaultValue = DefaultValue.Mock;的真正含义是Moq创建了CubeBuilder的模拟-换句话说,它动态地发出了一个从CubeBuilder派生的类。但是,当被要求创建一个SealedCubeBuilder的模拟时,它不会,因为它无法创建从密封类派生的类。

它不会引发异常,而是返回null。这是不一致的行为,并且是I've reported this as a bug in Moq

关于dependency-injection - 为什么在封闭类时,带有AutoMoqCustomization的Autofixture不再提示缺少无参数的构造函数?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/18155015/

10-13 03:07