当我在单元测试中直接使用Moq来模拟IBuilderFactory
和实例化BuilderService
时,我可以得到一个通过测试来验证IBuilderFactory
的Create()
方法是否被正确调用一次。
但是,当我在AutoMoqCustomization,中使用Auto治冻结IBuilderFactory
的模拟并用fixture.Create<BuilderService>
实例化BuilderService
时,我会得到以下异常:
System.ArgumentException:无法实例化类的代理: OddBehaviorTests.CubeBuilder。找不到无参数构造函数。参数名称: constructorArguments
如果我使CubeBuilder
密封(通过用IBuilderFactoryForSealedBuilder.Create()
调用的密封类SealedCubeBuilder
替换它来表示),那么测试将使用AutoFixture通过AutoMoqCustomization,没有异常抛出。
我是不是遗漏了什么?因为我直接使用Moq通过测试,所以我相信这与自动夹具和/或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 { }
发布于 2013-08-11 00:19:27
如果您查看堆栈跟踪,您会注意到异常发生在Moq内部。AutoFixture是一个固执己见的库,它持有的一个观点是,空值是无效的返回值。因此,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());
}
奇怪的是,它似乎仍然适用于密封类。然而,这并不完全正确,而是起源于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);
}
在这种情况下,尽管默示不返回null
,但Moq还是这么做了。
我的理论是,在上面的MoqCharacterizationForUnsealedClass中,factory.DefaultValue = DefaultValue.Mock;
的真正意思是Moq创建了一个对CubeBuilder的嘲弄,换句话说,它动态地发出一个从CubeBuilder派生出来的类。但是,当被要求创建SealedCubeBuilder的模拟时,它不能创建,因为它不能创建从密封类派生的类。
它不是抛出异常,而是返回null
。这是不一致的行为,和I've reported this as a bug in Moq。
https://stackoverflow.com/questions/18155015
复制