依赖注入 – 为什么Autofixture w / AutoMoqCustomization在类密封时停止抱怨缺乏无参数构造函数?

当我直接使用Moq来模拟IBuilderFactory并在单元测试中自己实例化BuilderService时,我可以得到一个通过测试,它验证IBuilderFactory的Create()方法只被调用一次.

但是,当我使用Autofixture与automoqCustomization,冻结IBuilderFactory的模拟并使用fixture.Create< BuilderService>实例化BuilderService时,我得到以下异常:

System.ArgumentException: Can not instantiate proxy of class: OddBehaviorTests.CubeBuilder. Could not find a parameterless
constructor. Parameter name: constructorarguments

如果我将CubeBuilder密封(用IBuilderFactoryForSealedBuilder.Create()调用的密封类SealedCubeBuilder代替它,测试将使用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没有认构造函数,并且没有安装程序告诉它如何处理对Create的调用.

(另外,具有讽刺意味的是,AutoFixture完全能够创建CubeBuilder的实例,但Moq中没有可扩展性点,使AutoFixture可以进入并接管Moq的认对象实例创建行为.)

现在,在密封返回类型时考虑此Characterization测试:

[Fact]
public void MoqCharacterizationForSealedClass()
{
    var factory = new Mock<IBuilderFactoryForSealedBuilder>();
    factory.DefaultValue = DefaultValue.Mock;
    var actual = factory.Object.Create();
    Assert.Null(actual);
}

事实证明,在这种情况下,尽管被隐含地告知不要返回null,但Moq仍然这样做.

我的理论是,真正发生的是在上面的MoqCharacterizationForUnsealedClass中,什么是factory.DefaultValue = DefaultValue.Mock;真正的意思是Moq创建了一个CubeBuilder的模拟 – 换句话说,它动态地发出一个派生自CubeBuilder的类.但是,当被要求创建一个SealedCubeBuilder的模拟时,它不能,因为它无法创建一个派生自密封类的类.

它不是抛出异常,而是返回null.这是不一致的行为,I’ve reported this as a bug in Moq.

相关文章

迭代器模式(Iterator)迭代器模式(Iterator)[Cursor]意图...
高性能IO模型浅析服务器端编程经常需要构造高性能的IO模型,...
策略模式(Strategy)策略模式(Strategy)[Policy]意图:定...
访问者模式(Visitor)访问者模式(Visitor)意图:表示一个...
命令模式(Command)命令模式(Command)[Action/Transactio...
生成器模式(Builder)生成器模式(Builder)意图:将一个对...