3
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[.NET] 単体テストがさくっと書ける!モック化の枠組み(Moq + Autofac)

Last updated at Posted at 2019-04-10

モックライブラリ Moq とIoCコンテナ Autofac を使用してモック化する例です。
単体テストの基底クラスにモックの生成、検証を抽出することで、Verify 漏れを防ぎ、素早く、すっきりテストコードを書くことができます。

※単体テストでDIコンテナを使用することの是非はここでは問いません。

■ライブラリ

■プロダクションコード

Autofac モジュール

ここでは、既定で protected プロパティに注入するように構成します。

internal class ProtectedPropertyInjectionModule : Autofac.Module
{
    private static readonly DelegatePropertySelector ProtectedSetterSelector =
        new DelegatePropertySelector((p, o) => p.CanWrite && (p.SetMethod?.IsFamily ?? false));

    protected override void AttachToComponentRegistration(
        IComponentRegistryBuilder componentRegistry,
        IComponentRegistration registration)
    {
        registration.PipelineBuilding += (sender, pipeline) =>
        {
            pipeline.Use(PipelinePhase.Activation, MiddlewareInsertionMode.EndOfPhase, (c, next) =>
            {
                next(c);

                c.InjectProperties(c.Instance, ProtectedSetterSelector);
            });
        };
    }
}

アプリケーション開始処理

DIを構成し、アプリケーションを開始します。
※フレームワークに応じた適切な箇所に記述してください。

public static void StartApplication()
{
    var builder = new ContainerBuilder();

    builder.RegisterModule(new ProtectedPropertyInjectionModule());
    builder.RegisterType<Service>().SingleInstance();
    builder.RegisterType<Client>();

    var container = builder.Build();

    var client = container.Resolve<Client>();
    var result = client.Act();

    Console.WriteLine(result);
    // Production
}

クライアントクラス(System Under Test)

public class Client
{
    /// <summary>
    /// 依存サービス。
    /// </summary>
    protected Service Service { get; set; }
 
    /// <summary>
    /// テスト対象メソッド。
    /// </summary>
    public string Act()
    {
        // プロパティ経由でDIコンテナから依存オブジェクトを取得して使用する。
        return this.Service.GetText();
    }
}

依存サービス(Depended-On Component)

public class Service
{
    private readonly string text = "Production";
 
    public virtual string GetText()
    {
        return this.text;
    }
}

■単体テスト

テストの基底クラス

モックオブジェクトの生成、DI登録、検証(Verify)を担います。

MSTest例
/// <summary>
/// 単体テストの基底クラス。
/// </summary>
public abstract class AutofacTestBase
{
    delegate void BuilderAction(ContainerBuilder builder);

    private BuilderAction registerMocks;

    private List<Mock> mocks;

    /// <summary>
    /// テストの初期処理。
    /// </summary>
    [TestInitialize]
    public void BaseTestInitialize()
    {
        this.mocks = new List<Mock>();
    }

    /// <summary>
    /// テストの終了処理。
    /// </summary>
    [TestCleanup]
    public void BaseTestCleanup()
    {
        // モックが期待どおりに呼び出されたことを検証する。
        foreach (var mock in this.mocks)
        {
            mock.VerifyAll();
        }
    }

    /// <summary>
    /// Mock インスタンスを生成する。
    /// </summary>
    protected Mock<T> CreateMock<T>()
        where T : class
    {
        return CreateMock<T>(false);
    }

    /// <summary>
    /// Mock インスタンスを生成する。
    /// </summary>
    protected Mock<T> CreateMock<T>(bool callBase)
        where T : class
    {
        var mock = new Mock<T> { CallBase = callBase };
        this.mocks.Add(mock);

        // RegisterMock はジェネリック引数(ここでは暗黙指定)から型を判定するので、登録処理自体をデリゲートに収める。
        // (List<Mock> では型を渡せない)
        this.registerMocks += builder => builder.RegisterMock(mock);

        return mock;
    }

    /// <summary>
    /// AutoMock オブジェクトを取得する。
    /// </summary>
    protected AutoMock GetLoose()
    {
        return AutoMock.GetLoose(builder =>
        {
            builder.RegisterModule(new ProtectedPropertyInjectionModule());
            this.registerMocks.Invoke(builder);
        });
    }
}

テストクラス

モックをDIコンテナに渡すので、依存コンポーネントを差し替えるためだけに Client を継承した Testable クラスを用意する必要はありません。

モックインスタンスは、個々のテストメソッドに必要なものだけ生成します。

MSTest例
[TestClass]
public class ClientTest : AutofacTestBase
{
    [TestMethod]
    public void Act_Mocking()
    {
        // モック設定
        var serviceMock = CreateMock<Service>();
        serviceMock.Setup(s => s.GetText()).Returns("UnitTest");

        // モックのDI登録
        using (var autoMock = GetLoose())
        {
            // テスト対象オブジェクトを生成
            var client = autoMock.Create<Client>();

            // テスト対象メソッドを実行
            string result = client.Act();

            // 戻り値の検証
            // ※モックの検証は基底クラスで自動的に行われる。
            Assert.AreEqual("UnitTest", result);
        }
    }
}

※掲載コードはサンプルですので、実際のプロジェクトでは、細かい点を調整してご活用ください。

※Unity 版は こちら をご覧ください。

3
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?