第一篇文章, 關於Mock的概念介紹: https://www.cnblogs.com/cgzl/p/9294431.html
本文介紹使用Moq來Mock方法.
使用的代碼: https://github.com/solenovex/Moq4-Tutorial-Code 里面的 02 Before 部分.
Mock 對象
緊接着上文中的例子. 上一篇文章, 我在單元測試的時候, 把依賴項設為null:
然后便出現了NullReferenceException, 導致測試無法正常運行.
首先應該做的是在TransferApproval的構造函數里判斷參數是否為null, 如果為null的話應該拋出ArgumentNullException:
這是更恰當的異常.
這樣的話, 在測試的時候, 拋出的就是ArgumentNullException了, 它可以更恰當的表達程序出現的問題:
現在我們可以使用mock版本的依賴項來代替null了:
上面的代碼首先使用Moq創建了一個mock版本的IPhysicalExamination的實例.
而由於Moq對依賴項進行了包裝, 所以要獲得實際的mock依賴項, 我們需要使用mockExamination.Object屬性. 而這個屬性的類型就是IPhysicalExamination.
另外一個測試方法我也這么改一下, 然乎重新Build. Run All Tests:
還是紅色的, 但現在是測試沒通過, 並不是拋出異常.
測試沒通過的意思就是期待值和實際返回值不符.
讓我們來調試一下這個測試, 我在TransferApproval類里面設置一個端點, 查看一下這個mock依賴項的方法返回值:
然后調試測試:
跑到斷點
可以看到這個Mock版本依賴項的IsHealthy()方法的返回值是false.
我並沒有對這個Mock版本的IPhysicalExamination的IsHealthy()方法設定返回值, 正因為如此, 它才會返回它方法返回類型的默認值, 它的返回類型是bool, 而bool的默認值是false, 所以現在IsHealthy()方法在沒有設定的情況下的返回值就是false.
It類
而PhysicalExamination這個具體的實現類由於各種原因導致還沒有實現, 為了讓它不妨礙我們的單元測試, 我先設定讓它在無論傳進什么參數的情況下都會返回true.
從業務上來講就是假設所有轉會球員都可以通過體檢:
那么現在所有的測試都應該可以通過了:
這里用到了It這個類, 在Moq里, It這個類是用來做參數匹配的, it 就是"它"的意思, 它就代表需要被匹配的參數.
It.IsAny<T>(), 它表示傳遞給方法的參數的類型只要是T就可以, 值是任意的. 只要滿足了這個條件, 那么方法的返回值就是后邊Returns()方法里設定的值.
Moq 關於It類的文檔: http://www.nudoq.org/#!/Packages/Moq/Moq/It
它有下面幾種用法:
- Is<TValue>(Expression<Func<TValue, Boolean>>)
- IsAny<TValue>()
- IsIn<TValue>(IEnumerable<TValue>)
- IsInRange<TValue>(TValue, TValue, Range)
- IsNotIn<TValue>(IEnumerable<TValue>)
- IsNotNull<TValue>()
- IsRegex(string)
我認為通過方法名就可以知道這些方法的用途.
下面我修改一下該測試方法, 使用It其它幾個方法:
其測試結果仍然是通過的.
嚴謹(Strict) vs 寬松(Loose) Mock
Moq里面有Strict(嚴謹)和Loose(寬松) mock對象的概念, 當然也有很多人不喜歡這個概念.
在當前的測試方法里, TransferApproval依賴於Mock<IPhysicalExamination>, 並調用其IsHealthy()方法.
如果不對IsHealthy()方法進行任何設定的情況下, 方法會返回bool的默認值false, 這種就是loose(寬松) Mock.
在創建Mock對象的時候, 還可選傳遞一個MockBehavior這個參數.
MockBehavior是一個枚舉, 它有三個值:
- MockBehavior.Strict, 如果mock對象上的方法沒有被預先設置好, 那么測試中調用該方法的時候就會拋出異常.
- MockBehavior.Loose, 即使方法沒有被預先設置, 調用它的時候也不會拋出異常. 它會返回該方法返回類型的默認值.
- MockBehavior.Default, 它代表MockBehavior.Loose.
如果上例使用Strict Mock, 那么將會拋出Exception:
下面我把一個測試改為Strict Mock, 並取消了對IsHealthy()方法的設置:
而測試時會拋出MockException:
在對方法進行設置后, 測試就會通過:
可以感覺到:
Loose Mock, 可以少寫一些設定代碼, 可以返回默認值, 不易讓測試中斷
Strict Mock, 需要寫跟多的設定代碼, 每個被調用的方法都需要進行設定, 所以也更容易讓測試中斷.
Moq的建議是: 大多數情況下應該使用Loose Mock, 只有特殊需要的時候才去使用Strict Mock.
out參數
修改一下TransferApproval類的轉會審批方法:
這次使用的是帶有out參數的IsHealthy()方法.
建立一個測試方法, 並設定這個帶有out參數的方法:
很簡單, 測試會通過:
完成的代碼在: https://github.com/solenovex/Moq4-Tutorial-Code 02 After
未完待續....