モックライブラリ Moq とDIコンテナ Unity1を使用してモック化する例です。
単体テストの基底クラスにモックの生成、検証を抽出することで、Verify 漏れを防ぎ、素早く、すっきりテストコードを書くことができます。
※単体テストで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 版は こちら をご覧ください。
-
ゲームエンジンの方ではありません。Microsoft patterns & practices のプロジェクトとして開発された軽量DIコンテナで、現在はその手を離れ、オープンソースで運営されています。 ↩