LoginSignup
1
5

More than 3 years have passed since last update.

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

Last updated at Posted at 2019-02-20

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

※1 ゲームエンジンの方ではありません。Microsoft patterns & practices のプロジェクトとして開発された軽量DIコンテナで、現在はその手を離れ、オープンソースで運営されています。

■プロダクションコード

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

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

public static void StartApplication()
{
    var container = new UnityContainer();

    container.RegisterSingleton<Service>();
    container.RegisterType<Client>();

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

    Console.WriteLine(result);
    // Production
}

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

public class Client
{
    /// <summary>
    /// 依存サービス。
    /// </summary>
    [Dependency]
    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例
public abstract class UnityTestBase
{
    private List<Mock> mocks;

    private IUnityContainer diContainer;

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

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

        this.diContainer.Dispose();
    }

    /// <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.diContainer.RegisterInstance(mock.Object);
        this.mocks.Add(mock);

        return mock;
    }

    /// <summary>
    /// テスト対象オブジェクトを構成する。
    /// </summary>
    protected void BuildUp<T>(T instance)
    {
        this.diContainer.BuildUp<T>(instance);
    }
}

テストクラス

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

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

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

        // テスト対象オブジェクトを生成
        var client = new Client();
        BuildUp(client);

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

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

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

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

1
5
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
1
5