その他の機能
最後に、Moqの便利なメソッド(Returns応用/AtMostOnce/Throws/Callback)について紹介します。
Moqの応用例(テストにパスします)
[TestMethod()]
public void EtcTest()
{
//(1)戻り値に変数を使用(Returns応用)
var mock1 = new Mock<IGreeting>();
string returnWord = "こんにちは";
mock1.Setup(m => m.Print("変数")).Returns(() => returnWord);
Assert.AreEqual("こんにちは", mock1.Object.Print("変数"));
returnWord = "おはよう";
Assert.AreEqual("おはよう", mock1.Object.Print("変数"));
//(2)1回のみ呼び出し可(AtMostOnce)
var mock2 = new Mock<IGreeting>();
mock2.Setup(m => m.Print("1回"))
.Returns("こんにちは").AtMostOnce();
Assert.AreEqual("こんにちは", mock2.Object.Print("1回"));
//もう一度Print("1回")を呼び出すとエラー
//(3)例外の発生(Throws)
var mock3 = new Mock<IGreeting>();
mock3.Setup(m => m.Print("例外")).Throws<ArgumentException>();
//mock3.Object.Print("例外")を呼び出すとArgumentException
//(4)処理の呼び出し(Callback)
var mock4 = new Mock<IGreeting>();
string log = string.Empty;
mock4.Setup(m => m.Print(It.IsAny<string>()))
.Callback(() => Console.WriteLine("呼び出し"))
.Returns("こんにちは")
.Callback>string>(s => log += s);
Assert.AreEqual("こんにちは", mock4.Object.Print("1回目"));
Assert.AreEqual("こんにちは", mock4.Object.Print("2回目"));
Assert.AreEqual("1回目2回目",log);
}
(1)の「Returns(() => returnWord)」ですが、これは戻り値にreturnWordという変数を使うように指定しています。これで戻り値を実行時に決定することができます。また変数に限らず「Returns((string s) => s + "あああ");」というように式を書くこともできます。
(2)では「AtMostOnce」と記述しています。これはメソッドを呼び出せる回数を1回に制限しています(2回目の呼び出しからはエラーとなります)。同様のメソッド「AtMost(n)」では、n+1回目からエラーとなります。
(3)の「Throws」では例外を設定しています。指定したメソッドを呼び出した時に例外が発生します。「Throws(new ArgumentException())」と書いても同様です。
(4)の「Callback」では任意の処理を記述できます。メソッド呼び出しのタイミングで、ログを出力したり、計算を行ったりすることができます。
モックの利用例
モックを積極的に活用すると、依存関係が複雑なクラスのテストを容易に書くことができます。例えば注文クラスの単体テストを書く時、依存する注文明細クラスや顧客クラスにモックを使用できます。
モックを使わない場合、単体テストというより結合テストに近くなってしまうことがあります。また、単体テストの内容が重複してしまうこともあります。モックを使えば、ターゲットクラスのテストに集中することができます。テストクラスのセットアップメソッドをシンプルにできるため、テストメソッド間の依存度を下げることもできます。
最後に
テスト時に、実クラスを使うかモックを使うかの判断は状況によって変わってきますが、モックという選択肢を知っておくことで、ユニットテストの設計に柔軟性を持たせることができます。
本稿では紹介しきれませんでしたが、Moqには、イベントのRaiseメソッド、引数のoutパラメータ、refパラメータ指定といった便利な機能も備わっています。興味を持たれた方は、ぜひMoqをダウンロードして試してみてはいかがでしょうか。