使用 Moq 測試.NET Core 應用 -- Mock 方法

solenovex發表於2018-07-13

第一篇文章, 關於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

未完待續....

 

相關文章