8

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

但是,当我将AutofixtureAutoMoqCustomization一起使用时,冻结模拟IBuilderFactory并用 实例化BuilderServicefixture.Create<BuilderService>我得到以下异常:

System.ArgumentException:无法实例化类的代理:OddBehaviorTests.CubeBuilder。找不到无参数构造函数。参数名称:constructorArguments

如果我进行CubeBuilder密封(通过将其替换为由 调用的密封类SealedCubeBuilder来表示IBuilderFactoryForSealedBuilder.Create(),则使用带有 AutoMoqCustomization 的 AutoFixture 通过测试,不会引发异常。

我错过了什么吗?由于我直接使用 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 { }
4

1 回答 1

11

如果您查看堆栈跟踪,您会注意到异常发生在 Moq 的深处。AutoFixture 是一个自以为是的库,它持有的一种观点是null 是无效的返回值。出于这个原因,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 的这个表征测试

[Fact]
public void MoqCharacterizationForUnsealedClass()
{
    var factory = new Mock<IBuilderFactory>();
    factory.DefaultValue = DefaultValue.Mock;
    Assert.Throws<ArgumentException>(() => factory.Object.Create());
}

Moq正确地抛出了一个异常,因为它被要求创建一个 CubeBuilder 的实例,但它不知道该怎么做,因为 CubeBuilder 没有默认构造函数,并且 noSetup告诉它如何处理对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. 这是不一致的行为,我已将此报告为 Moq 中的错误

于 2013-08-11T08:19:27.907 回答