【C#】Moqでvoid関数が呼ばれたかどうかをテストする



# Moqを使って単体テストをする
Moqとは単体テスト用の代役を準備するためのパッケージです。これを使うと実環境が必要なコードの部分を一時的に入れ替えることができ、テスト可能な状態にすることができます。

以下のようなコードの場合、どのようにメソッドが呼ばれたかどうかをチェックするのか気になったので調べてみました。実際にこのようなコードをテストする必要性があるのかどうかはよくわかりませんがとにかくやってみます。

```Original.cs
public class Original
    {
        public void checkMethodCalling(int num)
        {
            if (num < 5)
            {
                firstMethod();
            }
            else
            {
                secondMethod();
            }
        }

        public void firstMethod()
        {
            Console.WriteLine("first method is called");
        }

        public void secondMethod()
        {
            Console.WriteLine("second method is called");
        }

    }
```

与えられた数字が5未満なら最初のメソッドを、それ以上なら二番目のメソッドが呼ばれます。一番目のメソッドが呼ばれたかどうかをチェックするにはどのようにすればよいのでしょうか？NUnitを使ってテストしていきます。

# 手を加えずテストする
まずはそのままMockを作ってテストしてみます

```Test.cs
        [Test]
        public void TestOriginal()
        {
            //Arrange
            var mock = new Mock<Original>();
            //Act
            mock.Object.checkMethodCalling(1);
            //Assert
            mock.Verify(x => x.firstMethod(), Times.Once());
        }
```

このままではエラーが出ます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64828/22ee1601-3558-4dcd-c585-c2d87898d42e.png)
オーバーライドできないと怒られてしまいました。

# Override可能にする
元のOriginalクラスをオーバーライドできるようにすればテストができるようになるかもしれません。

```OriginalVirtual.cs

  public class OriginalVirtual
    {
        public void checkMethodCalling(int num)
        {
            if (num < 5)
            {
                firstMethod();
            }
            else
            {
                secondMethod();
            }
        }

        //override可能
        public virtual void firstMethod()
        {
            Console.WriteLine("first method is called");
        }

        public virtual void secondMethod()
        {
            Console.WriteLine("second method is called");
        }
    }

```
この形であればOverrideが可能です。実際にテストを書いてみましょう。

```Test.cs
[Test]
        public void FirstMethodCallingThroughOverride()
        {
            //Arrange
            var mock = new Mock<OriginalVirtual>();
            //Act
            mock.Object.checkMethodCalling(1);
            //Assert
            mock.Verify(x => x.firstMethod(), Times.Once());
        }

        [Test]
        public void SecondMethodCallingThroughOverride()
        {
            //Arrange
            var mock = new Mock<OriginalVirtual>();
            //Act
            mock.Object.checkMethodCalling(6);
            //Assert
            mock.Verify(x => x.secondMethod(), Times.Once());
        }
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64828/ae929981-b097-b1da-f973-56841bc36aff.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64828/15848822-c542-ce2e-555e-b83f15b6ecb5.png)

問題なくテストが通りました。ただ元のコードをvirtualにしたくないという場合があるかもしれません、その場合はInterfaceを使って書くこともできます。

# Interfaceを使ってテストを書く

インターフェイスを準備します
```IOriginal.cs
    public interface IOriginal
    {
        void checkMethodCalling(int num);
        void firstMethod();
        void secondMethod();
    }
```

インターフェイスを実装するクラスを準備します。
```OriginalWithInterface.cs
public class OriginalWithInterface : IOriginal
    {
        public void checkMethodCalling(int num)
        {
            if (num < 5)
            {
                firstMethod();
            }
            else
            {
                secondMethod();
            }
        }

        public void firstMethod()
        {
            Console.WriteLine("first method is called");
        }

        public void secondMethod()
        {
            Console.WriteLine("second method is called");
        }
    }
```

Moqを使ってインターフェイスを実装するモックを作ります。Moqはインターフェイスを準備してくれますが中身が空なのでそこは自分で準備をしてあげなくてはいけません。voidメソッドを呼び出すので、callbackを使って追加していきます。この二つのテストは[TestCase]あたりを使えば一つにできると思いますが、今回は両方とも書いています。

```Test.cs
[Test]
        public void FirstMethodCallingThroughInterface()
        {
            //Arrange
            var mock = new Mock<IOriginal>();
            mock.Setup(x => x.checkMethodCalling(It.IsAny<int>())).Callback((int num) =>
            {
                if (num < 5)
                {
                    mock.Object.firstMethod();
                }
                else
                {
                    mock.Object.secondMethod();
                }
            }
            );
            //Act
            mock.Object.checkMethodCalling(1);
            //Assert
            mock.Verify(x => x.firstMethod(), Times.Once());
        }

        [Test]
        public void SecondMethodCallingThroughInterface()
        {
            //Arrange
            var mock = new Mock<IOriginal>();
            mock.Setup(x => x.checkMethodCalling(It.IsAny<int>())).Callback((int num) =>
            {
                if (num < 5)
                {
                    mock.Object.firstMethod();
                }
                else
                {
                    mock.Object.secondMethod();
                }
            }
            );
            //Act
            mock.Object.checkMethodCalling(6);
            //Assert
            mock.Verify(x => x.secondMethod(), Times.Once());
        }
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64828/3f58f59b-a374-f3c4-6b36-a8d5f0484491.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64828/864fa5bd-14ab-298f-9e66-1358a90476a0.png)

テストが通りました。

:::note warn
あくまでもfirstMethodまたはsecondMethodが呼ばれたかどうかを確かめるだけで、これらのメソッドが正しいかどうかは別にテストする必要があります。
:::

例えば、以下の部分は別途テストが必要です、なのでConsole.WriteLineの部分をコメントアウトしてもテストが通ります。
```Original.cs
public void firstMethod()
        {
            Console.WriteLine("first method is called");
        }

        public void secondMethod()
        {
            //コメントアウトしてもテストが通る。
            //Console.WriteLine("second method is called");
        }
```

# おわりに
今回はvoidメソッドの中でvoidメソッドが呼ばれている場合どのようにメソッドが呼ばれたかどうかをテストしました。実際にこのようにテストをするのがよいのかどうかはわかりませんが、やり方が分からなかったので試してみました。