2014年6月3日 星期二

Moq 一個用來模擬物件的類別庫

當系統利用介面完成物件來隔離外部物件之後,為了方便測試通常會寫一些假的物件來抽換
但功能越來越多之後,測試的假類別也越多,檔案不好管理
這時後可以讓Moq利用反射的方式讓我們很容易地新增假物件
Moq只能模擬公開的介面,如果是繼承的類別則需要Virtual才能模擬

要使用Moq很簡單,利用NuGet安裝套件就行了

用一個簡單的IFoo介面來當例子
public interface IFoo
{
    void F1();
    int F2();
    int F3(int x);
    event EventHandler<object> MyEvent;
}

首先建立一個IFoo型別的Mock物件
在建構式中可以指定MockBehavior來設定模擬物件的程度
Strict是限制需要完整模擬物件,Loose則不需要
預設為Default,也就是Loose
private Mock<IFoo> mock;

[TestInitialize]
public void MyTestInitialize()
{
    this.mock = new Mock<IFoo>();
}

一開始先用沒有回傳值的函式當例子,就先不做更多的設定
直接使用Mock物件的Object屬性取得模擬的假物件就可以使用了
這裡用Verify和Times來檢查該函式的呼叫次數
[TestMethod]
public void TestMethod1()
{
    Mock<IFoo> foo = new Mock<IFoo>();
    foo.Object.F1();
    foo.Verify(x => x.F1(), Times.Once());
}

再來用一個有回傳值的函式當例子,所以再加上回傳值的設定
這裡的意思是呼叫F2函式的時後,總是回傳123的值回來
[TestMethod]
public void TestMethod2()
{
    Mock<IFoo> foo = new Mock<IFoo>();
    foo.Setup(x => x.F2()).Returns(123);
    Assert.AreEqual(123, foo.Object.F2());
}

函式中的參數可以透過It物件來設定
這裡的意思是只要傳入int型別的參數,就回傳456的值
[TestMethod]
public void TestMethod3()
{
    Mock<IFoo> foo = new Mock<IFoo>();
    foo.Setup(x => x.F3(It.IsAny<int>())).Returns(456);
    Assert.AreEqual(456, foo.Object.F3(1));
}

除了回傳值之外,也可以設定Callback函式
[TestMethod]
public void TestMethod4()
{
    int counter = 0;
    Mock<IFoo> foo = new Mock<IFoo>();
    foo.Setup(x => x.F1()).Callback(() => counter++);
    foo.Object.F1();
    Assert.AreEqual(1, counter);
}

還有丟回指定的Exception
[TestMethod]
[ExpectedException(typeof(Exception))]
public void TestMethod5()
{
    Mock<IFoo> foo = new Mock<IFoo>();
    foo.Setup(x => x.F1()).Throws(new Exception());
    foo.Object.F1();
}

還有引發指定的事件
[TestMethod]
public void TestMethod6()
{
    Mock<IFoo> foo = new Mock<IFoo>();
    foo.Raise(x => x.MyEvent += (s, a) => { Console.WriteLine(a); }, 123);
}


參考資料
Moq Quickstart