当我在单元测试中直接使用 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/