3

自動モッキングに関するMark Seeman の記事を読み、その記事に基づいて再利用可能なウィンザー コンテナーを作成しています。

マークの記事の私の実装 (基本的に直接コピー)

主な作業はAutoMoqResolver教室で行います。これにより、クラスがインターフェースに依存するたびにモックが提供されます。

public class AutoMoqResolver : ISubDependencyResolver
{
    private readonly IKernel kernel;

    public AutoMoqResolver(IKernel kernel)
    {
        this.kernel = kernel;
    }

    public bool CanResolve(
        CreationContext context,
        ISubDependencyResolver contextHandlerResolver,
        ComponentModel model,
        DependencyModel dependency)
    {
        return dependency.TargetType.IsInterface;
    }

    public object Resolve(
        CreationContext context,
        ISubDependencyResolver contextHandlerResolver,
        ComponentModel model,
        DependencyModel dependency)
    {
        var mockType = typeof(Mock<>).MakeGenericType(dependency.TargetType);
        return ((Mock)this.kernel.Resolve(mockType)).Object;
    }
}

は、次のインターフェイスAutoMoqResolverの実装を使用してコンテナーに追加されます。IWindsorInstaller

public class AutoMockInstaller<T> : IWindsorInstaller
{
    public void Install(
        IWindsorContainer container,
        IConfigurationStore store)
    {
        container.Kernel.Resolver.AddSubResolver(
            new AutoMoqResolver(container.Kernel));

        container.Register(Component.For(typeof(Mock<>)));

        container.Register(Classes
            .FromAssemblyContaining<T>()
            .Pick()
            .WithServiceSelf()
            .LifestyleTransient());
    }
}

次に、私のコンテナーはインストーラーを実行するだけで、単体テストでインターフェイスの依存関係のモックを自動的に提供する準備が整います。

public class AutoMockContainer<T> : WindsorContainer
{
    public AutoMockContainer()
    {
        // simply run the auto-mock installer
        this.Install(new AutoMockInstaller<T>());
    }
}

素晴らしい!

これをテストしたところ、依存関係が自動的に喜んでモック化されたので、実際のコードに適用しました。これは、クラスをテストするときに従う傾向があるパターンのために、ソリューションが役に立たないことに気付いたときです。私の具体的な問題は、SUT のあるメソッドが別のメソッドから呼び出されていることを確認するために、SUT 自体を自動モックできるようにしたいということです。

テストする必要がある私のコード

例を挙げて説明します。私は MVC コードを開発しており、次の一般的なパターンを使用して目立たない AJAX をサポートしています。

public Class ExampleController : Controller
{
    private IService service;

    public ExampleController(IService service)
    {
        this.service = service;
    }

    public PartialViewResult DoSomethingWithAjax()
    {
        this.PerformTask();

        return this.PartialView();
    }

    public RedirectToRouteResult DoSomethingWithoutAjax()
    {
        this.PerformTask();

        return this.RedirectToAction("SomeAction");
    }

    protected virtual void PerformTask()
    {
        // do something here
    }
}

私のテストパターン

PerformTask()メソッドがDoSomethingWithAjax()orから呼び出されたことを確認するために、次のようなDoSomethingWithoutAjax()新しいTestableExampleControllerクラスを定義します。

public class TestableExampleController : ExampleController
{
    public TestableExampleController(IService service) : base(service)
    {
    }

    public virtual void PerfomTaskPublic()
    {
        base.PerfomTask();
    }

    protected override void PerformTask()
    {
        this.PerformTaskPublic();
    }
}

TestableExampleController次のテストに合格するように、SUT として使用できます。

[TestMethod]
public void DoSomethingAjax_Calls_PerformTask()
{
    //// Arrange
    // create a mock TestableExampleController
    var controllerMock = new Mock<TestableExampleController>();
    controllerMock.CallBase = true;

    // use the mock controller as the SUT
    var sut = controllerMock.Object;

    //// Act
    sut.DoSomethingAjax();

    //// Assert
    controllerMock.Verify(x => x.PerformTaskPublic(), Times.Once());
}

私の問題

このテストをリファクタリングして、次AutoMockContainerのようにクラスを使用してもうまくいきません:

[TestMethod]
public void DoSomethingAjax_Calls_PerformTask()
{
    //// Arrange
    // create a container
    var container = new AutoMockContainer<TestableExampleController>();

    // resolve a mock SUT using the container
    var controllerMock = container.Resolve<Mock<TestableExampleController>>();
    controllerMock .CallBase = true;

    // use the mock controller as the SUT
    var sut = controllerMock.Object;

    //// Act
    sut.DoSomethingAjax();

    //// Assert
    controllerMock.Verify(x => x.PerformTaskPublic(), Times.Once());
}

Mock<TestableExampleController>パラメーターなしのコンストラクターが見つからないため、テストは のインスタンスの作成に失敗します。

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

私の提案する解決策

理想的には、コンテナーに登録して任意のコンポーネントに自動的にモックを提供できるラッパー クラスを実装したいと考えています。

public class ComponentWrapper<T> where T : class
{
    public ComponentWrapper(Mock<T> componentMock)
    {
        componentMock.CallBase = true;
        this.ComponentMock = componentMock;
    }

    public Mock<T> ComponentMock { get; private set; }

    public T Component
    {
        get { return this.ComponentMock.Object;  }
    }
}

合格する次のテストを記述できるようにしたいと思います。

[TestMethod]
public void DoSomethingAjax_Calls_PerformTask()
{
    //// Arrange
    // create a container
    var container = new AutoMockContainer<TestableExampleController>();

    // resolve a ComponentWrapper using the container
    var wrapper = container.Resolve<ComponentWrapper<TestableExampleController>>();

    //// Act
    // call a method using the component
    wrapper.Component.DoSomethingAjax();

    //// Assert
    // verify a method call using the mock
    wrapper.ComponentMock.Verify(x => x.PerformTaskPublic(), Times.Once());
}

これを達成する方法がよくわかりません。新しい ISubDependencyResolver の実装をいじるのにほとんどの時間を費やしましたが、これを機能させることができません。

うまくいけば、私の質問は明確で、答えは実際には比較的簡単ですか?

4

1 に答える 1

5

AutoFixture.AutoMoqは、箱から出してすぐにやりたいことを正確に実行することがわかったので、正しい方向に向けてくれた TrueWill に感謝します。

次の簡単なテストに合格します。

[TestMethod]
public void Run_Calls_DoSomethingProtected()
{
    //// Arrange
    // AutoMoqCustomization allows AutoFixture to 
    // be used an an auto-mocking container
    var fixture = new Fixture().Customize(new AutoMoqCustomization());

    // simply ask the fixture to create a mock
    var sutMock = fixture.Create<Mock<TestableDummySystem>>();

    //// Act
    // exercise the mock object
    sutMock.Object.Run();

    //// Assert
    // this verification passes!
    sutMock.Verify(x => x.DoSomethingProtectedPublic());
}
于 2013-05-28T09:26:13.653 に答える