Dependency injection 当类被密封时,为什么Autofixture w/AutoMoqCustomization停止抱怨缺少无参数构造函数?
当我在单元测试中直接使用Moq模拟Dependency injection 当类被密封时,为什么Autofixture w/AutoMoqCustomization停止抱怨缺少无参数构造函数?,dependency-injection,moq,castle-dynamicproxy,autofixture,automocking,Dependency Injection,Moq,Castle Dynamicproxy,Autofixture,Automocking,当我在单元测试中直接使用Moq模拟ibailderfactory并实例化BuilderService时,我可以得到一个通过的测试,该测试验证ibailderfactory的Create()方法只调用了一次 但是,当我将Autofixture与AutoMoqCustomization一起使用时,冻结了IBuilderFactory的模拟,并使用fixture.Create实例化了BuilderService,我得到了以下异常: System.ArgumentException:无法实例化类OddB
ibailderfactory
并实例化BuilderService
时,我可以得到一个通过的测试,该测试验证ibailderfactory
的Create()
方法只调用了一次
但是,当我将Autofixture与AutoMoqCustomization一起使用时,冻结了IBuilderFactory的模拟,并使用fixture.Create
实例化了BuilderService
,我得到了以下异常:
System.ArgumentException:无法实例化类OddBehaviorTests.CubeBuilder的代理。找不到无参数的
构造器。参数名称:构造函数参数
如果我将CubeBuilder
密封(用ibilderfactoryforsealedbuilder.Create()
调用的密封类SealedCubeBuilder
替换它),则使用AutoFixture和AutoMoqCustomization通过测试,不会引发异常
我遗漏了什么吗?因为我直接使用Moq通过测试,我相信这与自动夹具和/或自动Moq定制有关。这是期望的行为吗?如果是,为什么
为了复制,我使用:
使用Moq;
使用Ploeh.AutoFixture;
使用Ploeh.AutoFixture.AutoMoq;
使用Xunit;
以下是四项测试,说明了该行为:
公共类构建器服务测试{
[事实]
应使用MOQ()调用公共void CubeBuilderFactoryCreateMethod{
var factory=new Mock();
var sut=newbuilderservice(factory.Object);
sut.Create();
factory.Verify(f=>f.Create(),Times.Once());
}
[事实]
应使用AutoFixture()调用公共void CubeBuilderFactoryCreateMethod{
var fixture=new fixture().Customize(new AutoMoqCustomization());
var factory=fixture.Freeze();
var sut=fixture.Create();
sut.Create();//引发异常!!
factory.Verify(f=>f.Create(),Times.Once());
}
[事实]
应使用MOQ()调用公共无效密封的CubeBuilderFactoryCreateMethod{
var factory=new Mock();
var sut=newbuilderServiceForSealedBuilder(factory.Object);
sut.Create();
factory.Verify(f=>f.Create(),Times.Once());
}
[事实]
应使用AutoFixture()调用公共无效密封的CubeBuilderFactoryCreateMethod{
var fixture=new fixture().Customize(new AutoMoqCustomization());
var factory=fixture.Freeze();
var sut=fixture.Create();
sut.Create();
factory.Verify(f=>f.Create(),Times.Once());
}
}
以下是所需的课程:
公共接口IBuilderService{IBuilder Create();}
公共类构建器服务:IBuilderService{
私人只读IBuilderFactory(工厂);
公共构建器服务(IBuilderFactory){u factory=factory;}
公共IBuilder Create(){return\u factory.Create();}
}
公共类构建器ServiceForSealedBuilder:IBuilderService{
私有只读IBuilderFactoryForSealedBuilder\u工厂;
公共构建器ServiceForsealedBuilder(iBilderFactoryForsealedBuilder工厂){u factory=factory;}
公共IBuilder Create(){return\u factory.Create();}
}
sealedBuilder{SealedCubeBuilder Create();}的公共接口IBILderFactory
公共接口IBuilderFactory{CubeBuilder Create();}
公共接口IBuilder{void Build();}
公共抽象类生成器:IBuilder{
public void Build(){}//Build stuff
}
公共类CubeBuilder:Builder{
私有多维数据集;
公共CubeBuilder(多维数据集){u Cube=Cube;}
}
公共密封类密封立方体生成器:生成器{
私有多维数据集;
公共密封立方体生成器(立方体){{u Cube=Cube;}
}
公共类多维数据集{}
如果查看堆栈跟踪,您会注意到异常发生在Moq的深处。AutoFixture是一个固执己见的库,它持有的观点之一是null是无效的返回值。。因此,AutoMoqCustomization将所有模拟实例配置为:
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());
}
事实证明,在这种情况下,尽管被隐式告知不要返回null
,Moq还是这样做了
我的理论是,在上面的MoqCharacterizationforUnseledClass中,factory.DefaultValue=DefaultValue.Mock;
真正的意思是Moq创建了一个CubeBuilder的模拟-换句话说,它动态地发出一个从CubeBuilder派生的类。然而,当被要求创建一个Sealed CubeBuilder的模拟时,它不能,因为它不能创建从密封类派生的类
它没有抛出异常,而是返回null
。这是不一致的行为,而且。感谢您破译了这一个!当答案被证明是我正在使用的库中的一个bug时,我的心情很复杂…我偶然发现了这一点,因为我的一些XXXBuilder类被密封了,一些没有。发现了关于是什么的争论在没有充分理由不密封的情况下密封类,或者做相反的事情。似乎密封类会导致单元测试摩擦-小心分享对这个问题的看法?密封类损害可测试性的概念起源于(我认为)来自Java,默认情况下所有成员都是虚拟的。在C#中,情况并非如此,因此问题变得不那么重要。在任何情况下,如果你喜欢组合而不是继承,这几乎不重要。FWIW、F#构造(如记录)编译为密封类,但F#代码仍然是非常可测试的。最终,如果你遵循TDD,你将拥有可测试代码:)我应该澄清,当使用中的模拟框架无法运行时,密封类似乎会损害可测试性
[Fact]
public void MoqCharacterizationForUnsealedClass()
{
var factory = new Mock<IBuilderFactory>();
factory.DefaultValue = DefaultValue.Mock;
Assert.Throws<ArgumentException>(() => factory.Object.Create());
}
[Fact]
public void MoqCharacterizationForSealedClass()
{
var factory = new Mock<IBuilderFactoryForSealedBuilder>();
factory.DefaultValue = DefaultValue.Mock;
var actual = factory.Object.Create();
Assert.Null(actual);
}