見出し画像

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

モックライブラリ 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 版は こちら をご覧ください。

この記事が気に入ったらサポートをしてみませんか?