Moq(モックライブラリー)を使ってみる。

0.前置き

ASP.NET MVCの開発ではコンポーネントテスト(単体テスト、ユニットテスト)が大事です。
# いつでも大事ですけど…。
特にデータアクセスする部分のモックを簡単に作れると、開発の時に幸せになれます。
今回はMoqというモックライブラリーを紹介してみたいと思います。

1.Moqとは

Moqは、Mock-Youと読みます。モッキューですね。なんか若干呼びにくい笑
開発プロジェクトはGoogleCode上にホストされています。こちらです。
Moqは強い型付けでモックを作ることができるので、初心者(?)にも簡単に使い始めることができます。
実際のプロジェクトでも利用していますが、機能的に不足を感じたことはないです。

2.インストール

Moqはnuget経由でインストールできます。
簡単。nuget、素晴らしい。

3.実際に使ってみる

インストールができたので、さっそく使ってみます。

3-1.テスト対象のクラスを作成する

テスト対象のクラスは以下のようです。
ProductLogicが、ProductDaoを経由してDBにアクセスするっていう感じです。

namespace MoqSample
{
    /// <summary>
    /// テスト対象のクラス
    /// </summary>
    public class ProductLogic
    {
        public IProductDao ProductDao { get; set; }

        public decimal CalculateTax(int id)
        {
            var domain = ProductDao.GetItemById(id);
            var result = domain.Price * 0.05m;
            return result;
        }

        public void EditProduct(Product item)
        {
            var id = item.Id;
            var domain = ProductDao.GetItemById(id);
            domain.Price *= 1.05m;
            ProductDao.Insert(domain);
        }
    }

    /// <summary>
    /// モックで置き換える予定のDaoのインターフェイス
    /// </summary>
    public interface IProductDao
    {
        void Update(Product product);
        void Insert(Product product);
        Product GetItemById(int id);
        List<Product> GetAllItems();
    }

    public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public decimal Price { get; set; }
    }
}

つづいて、このProductLogicに対するテストを作成します。

3-2.戻り値があるメソッドのテストをする

最初はテスト対象のメソッドが戻り値を持つ場合です。
この場合はテスト対象の出力値をAssertすればOKです。

[TestMethod]
public void CalculateTaxTest()
{
    //Arrange
    var dao = new Mock<IProductDao>();
    dao.Setup(m => m.GetItemById(1))
        .Returns(new Product
        {
            Id = 1,
            Name = "Book",
            Price = 300
        });
    var target = new ProductLogic { ProductDao = dao.Object };

    //Act
    var result = target.CalculateTax(1);

    //Assert
    Assert.AreEqual(15, result);
}

3-3.戻り値がvoidのメソッドをテストする

テスト対象が戻り値を持たない場合は、検証をどのように行うか悩んでしまいます。
今回はテスト対象の処理の最後に呼ばれるInsertメソッドに着目し、Insertメソッドに渡された値を検証します。
ここではCallBackを利用し、入ってきた値を検証しています。

[TestMethod]
public void EditProductTest()
{
    //Arrange
    //DBアクセス部分をモックで作成
    var dao = new Mock<IProductDao>();
    dao.Setup(m => m.GetItemById(10))
        .Returns(new Product
                        {
                            Id = 10,
                            Name = "Book",
                            Price = 300
                        });
    dao.Setup(m => m.Insert(It.IsAny<Product>()))
        .Callback<Product>(p =>
                                {
                                    //コールバックにてAssertを実行
                                    Assert.AreEqual(10, p.Id);
                                    Assert.AreEqual("Book", p.Name);
                                    Assert.AreEqual(315, p.Price);
                                });
    var target = new ProductLogic { ProductDao = dao.Object };

    //Act
    target.EditProduct(new Product { Id = 10 });

    //Assert(一部)
    dao.Verify(m => m.Insert(It.IsAny<Product>()), Times.Once());
    dao.Verify(m => m.Update(It.IsAny<Product>()), Times.Never());
}

4.サンプルを書いてみての補足

CallBackで検証コードを入れる場合は、ちゃんとその検証コードが呼ばれたかチェックする必要があります。
もし呼ばれていなかったら、検証コードを作りこんだ意味がないですからね。
そういうわけで、dao.Verifyを使って、モックが何回呼ばれたかをチェックします。

5.サンプルコード

サンプルコードはgithubにホスティングしてあります。
AutoMapperのサンプル
コード本体

6.最後に

最近コンポーネントテストが楽しい。
いまはまだMoqしか試していないので、他のテストフレームワークも触ってみたいですね。
あとJavaScriptのテストフレームワークにも挑戦してみたいです。