やりたいこと
以前の記事でMoqライブラリを使ってUTをする超便利な方法を知ったが、 最近、コールバックのメソッドを登録するためのメソッドを持つクラス、Interfaceがあり、それをMoqでテストしたい、となった。
前回調べたやり方だと、あるメソッドを指定の引数で呼び出したらこの戻り値を返してね、的な使い方しかしなかったので、登録したコールバックを呼んでくれるようなMockを作りたい。
前提
VisualStudio2022/.NET6/C#/Moq4.17.2
イメージ
文字で上のように書いてもよくわからないので、具体的には下記のようなクラス/interfaceをテストしたい。
// インターフェース public interface IMyClass { void RegistFunc(Action<int> func); } // インターフェースを実装したクラスのサンプル public class MyClass1 : IMyClass { private Action<int> _func; public void RegistFunc(Action<int> func) { _func = func; // ★このfunc(コールバックメソッド)を、任意のタイミングで本来は呼ぶ } }
サンプルコード
using Moq; // モックを作る var mc = new Mock<IMyClass>(); mc.Setup(x => x.RegistFunc(It.IsAny<Action<int>>())) .Callback<Action<int>>(p => { int i = 0; while (true) { // 1秒おきに、RegistFunc()で登録したメソッドを呼んでやる p.Invoke(i++); Thread.Sleep(1000); } }); // モックをinterfaceに入れる IMyClass imc = mc.Object; // 引数で受けたintを2乗して返すメソッドを登録 imc.RegistFunc(p => { Console.WriteLine(p * p); }); Console.ReadLine();
ポイント
要点は、下記の部分。
追記
上のサンプルだと、
imc.RegistFunc(p => { Console.WriteLine(p * p); });
を読んだ時点で、.Callback<Action<int>>(p =>
に書いたメソッドが呼ばれ、そののなかのwhile (true)
の無限ループに入ってしまうのでConsole.ReadLine()
にはたどり着かない。
この場合、もしwhile (true)
の中の処理は継続しつつ、他の処理を続けたいときは、mc.Setup(x => x.RegistFunc(・・・
の部分を
mc.Setup(x => x.RegistFunc(It.IsAny<Action<int>>())) .Callback<Action<int>>(p => { int i = 0; Task.Run(() => { while (true) { p.Invoke(i++); Thread.Sleep(1000); } }); });
という感じで、別スレッドに投げっぱなしにする。