コールバックメソッドで登録したメソッドを呼んでやるようなMockをつくる

tera1707.com

やりたいこと

以前の記事で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();

ポイント

要点は、下記の部分。

f:id:tera1707:20220317231849p:plain

追記

上のサンプルだと、

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);
            }
        });
    });

という感じで、別スレッドに投げっぱなしにする。

参考

github.com