TDD を実施するために Moq を試してみた 　その１基礎編

C# で頻繁に出てくる Mock Framework の [Moq](https://github.com/Moq/moq4/wiki/Quickstart) を試してみた。どうやら、先日試していた [Autfac とも連携](http://docs.autofac.org/en/latest/integration/moq.html)するようだ。Moq, Autofac がある程度理解できて、Linq が書けるようになれば、C# で、そこそこ Test Driven Development ができるかもしれない。

最初にお試しのために、こんなインターフェイスやクラスを書いてみた。

```csharp

using System;
using Moq;

    public interface IProudct
    {
        string GetContent(string url);
    }
```

これに対して、Mock のプログラムを書いて試してみる。

# 基本的な Mock 

見ての通り、インターフェイスしかないが、インターフェイスに対して Mock を自動生成できる。テスト駆動開発をする場合は、単体テスト時に外部インターフェイスなどは、Mock したいところだが、自分で MockObject を作るのは面倒くさい。Moq があれば、自動的にやってくれる。

```csharp
// Interface mocking
var productMock = new Mock<IProudct>();
productMock.Setup(product => product.GetContent("http://www.yahoo.co.jp")).Returns("This is html.");
Console.WriteLine("#[MoqBasic] http://www.yahoo.co.jp:" + productMock.Object.GetContent("http://www.yahoo.co.jp"));
Console.WriteLine("#[MoqBasic] http://www.foo.bar:" + productMock.Object.GetContent("http://www.foo.bar"));
```

しかもとてもいいことに、入力値と戻り値を書いておけば、その通りにモックしてくる。実際に Moq を使う場合は、`productMock.Object.GetContent` のように、一旦 Object メソッド経由して、モックを取得して使う。

```csharp
productMock.Setup(product => product.GetContent("http://www.yahoo.co.jp")).Returns("This is html.");
```

実際にクラスを実行してみよう。Mockした通りの値が出力されている。Setup メソッドでセットしていない引数で実行した場合は、空で帰ってくるようだ。

```
#[MoqBasic] http://www.yahoo.co.jp:This is html.
#[MoqBasic] http://www.foo.bar:
```

# クラスの Mock

では、インターフェイスに対する Mock はできたが、クラスに対してはどうだろうか？

```csharp
    public class ProductImpl : IProudct
    {
        public virtual string GetContent(string url) // virtual が必要
        {
            return $"{url}: <html><head><title>hello</title></head><body>Hello!</body></html> "; 
        }
    }
```

モックのSetup は全く同様だ。

```csharp
// Class mocking

var productImplMock = new Mock<ProductImpl>();
productImplMock.Setup(product => product.GetContent("http://www.yahoo.co.jp")).Returns("This is html.");
Console.WriteLine("#[MoqBasic] http://www.yahoo.co.jp:" + productImplMock.Object.GetContent("http://www.yahoo.co.jp"));
Console.WriteLine("#[MoqBasic] http://www.foo.bar:" + productImplMock.Object.GetContent("http://www.foo.bar"));
```

実行結果は次の通り

```
#[MoqBasic] http://www.yahoo.co.jp:This is html.
#[MoqBasic] http://www.foo.bar:
```

全く同じだ。Setup で指定しなければ、元のメソッドが呼ばれるわけではないのに注意。また、一点だけ注意点があり、クラスを Mock したい場合は、メソッドに `virtual`キーワードが必要だ。これを無くすと

```
System.NotSupportedException: 'Invalid setup on a non-virtual (overridable in VB) member: product => product.GetContent("http://www.yahoo.co.jp")'
```
という感じのエラーが出る。Mock で、メソッドをオーバーライドするのに、virtual がついていないと override 出来ないからだ。

# ネストしたオブジェクトの Mock

ちなみに、クラスがさらに、他のクラスを持っているようなケースがあるが、その場合も、ネストしたオブジェクト毎 Mock してくれる。先ほどのプログラムを改造してみよう。

```csharp

    public class ProductType
    {
        public virtual int Id { get; set; }
        public virtual string Name { get; set; }
    }
    public interface IProudct
    {
        string GetContent(string url);
        ProductType ProductType { get; set; }

    }
```
Mock 部

```csharp

var productMock = new Mock<IProudct>();

// Nested Object Mocking
productMock.Setup(product => product.ProductType.Name).Returns("Suppliment");
Console.WriteLine("#[MoqBasic] IProduct.ProductType.Name:" + productMock.Object.ProductType.Name);
```

実行結果は

```
#[MoqBasic] IProduct.ProductType.Name:Suppliment
```

というわけで、ネストしたオブジェクトもむっちゃ簡単に Mock できる！これはいいね！

# Mock メソッドが適切な値で呼ばれたかの確認

TDD をやっていて、Mock を使う場面では、偽物のオブジェクトを作るだけじゃなくて、その偽物オブジェクトに、適切な値がわかってきたか？を確かめたい場合がある。そのために `verify` というメソッドが用意されているようだ。

```csharp

// Validate the parameter
try
{
    productMock.Verify(product => product.GetContent("http://www.yahoo.co.jp"));
    productMock.Verify(product => product.GetContent("http://www.microsoft.com"));
} catch (MockException ex)
{
    Console.WriteLine("#[MoqBasic]" + ex.Message);
}
```

こんな感じで書いておくと、結果は次の通り。基本的に、予想した値が Mock にわたっていなければ例外が発生する。

```
Expected invocation on the mock at least once, but was never performed: product => product.GetContent("http://www.microsoft.com")

Configured setups:
product => product.GetContent("http://www.yahoo.co.jp")

Performed invocations:
IProudct.GetContent("http://www.yahoo.co.jp")
IProudct.GetContent("http://www.foo.bar")
IProudct.ProductType
```

最低限だったが、この最低限のセットがあれば、既にテスト駆動を始められるだろう。他にも、CallBack や Event さらに Linq に対しても Mock がかけられるらしいので、次回以降で試していきたい。

今回の動作するコードは[こちら](https://github.com/TsuyoshiUshio/MoqSamples)に置いておいたので参考にしてください。