8

単体テストでMoqを直接使用して自分自身をモックIBuilderFactoryおよびインスタンス化すると、のメソッドが1 回だけ呼び出されるBuilderServiceことを検証する合格テストを取得できます。Create()IBuilderFactory

ただし、AutoMoqCustomizationAutofixtureを使用し、 のモックをフリーズしてIBuilderFactoryでインスタンス化BuilderServiceするとfixture.Create<BuilderService>、次の例外が発生します。

System.ArgumentException: クラスのプロキシをインスタンス化できません: OddBehaviorTests.CubeBuilder. パラメーターなしのコンストラクターが見つかりませんでした。パラメータ名: constructorArguments

私が封印した場合(によって呼び出されるCubeBuilder封印されたクラスに置き換えることで表されます)、テストは AutoFixture を使用して AutoMoqCustomization に合格し、例外はスローされません。SealedCubeBuilderIBuilderFactoryForSealedBuilder.Create()

何か不足していますか?Moq を直接使用してテストに合格するので、これは Autofixture および/または AutoMoqCustomization に関連していると思います。これは望ましい動作ですか?もしそうなら、なぜですか?

再現するために、私は使用しています:

using Moq;
using Ploeh.AutoFixture;
using Ploeh.AutoFixture.AutoMoq;
using Xunit;

動作を示す 4 つのテストを次に示します。

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 は独断的なライブラリであり、それが保持する意見の 1 つは、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 のインスタンスを作成するように求められているため、正しくSetup例外をスローします。CubeBuilder にはデフォルトのコンストラクターがなく、呼び出しの処理方法が指示されていないため、Moq はその方法を知りません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で、Moq が CubeBuilder のモックfactory.DefaultValue = DefaultValue.Mock;を作成すること、つまり、CubeBuilder から派生したクラスを動的に発行することです。ただし、 SealedCubeBuilder のモックを作成するように求められた場合、sealed クラスから派生したクラスを作成できないため、作成できません。

例外をスローする代わりに、 を返しますnull。これは一貫性のない動作であり、これを Moq のバグとして報告しました

于 2013-08-11T08:19:27.907 に答える