第一篇文章, 關於Mock的概念介紹: https://www.cnblogs.com/cgzl/p/9294431.html
第二篇文章, 關於方法Mock的介紹: https://www.cnblogs.com/cgzl/p/9300356.html
本文介紹Moq的使用.
使用的程式碼: https://github.com/solenovex/Moq4-Tutorial-Code 裡面的 03 Before 部分.
Mock屬性
屬性是指 get set property.
接著上文, 我在03 Before部分的程式碼裡做了一些修改.
首先IPhysicalExamination介面新增了IsMedicalRoomAvailable屬性:
其實現類:
屬性方法內依然沒有做實現.
新增的這個屬性在業務上的意思就是體檢室是否可以使用. 如果不可以使用的話, 那麼球員的轉會操作應該被推遲.
所以還需要為轉會結果列舉新增一個推遲:
最後在轉會審批邏輯裡進行判斷, 如果體檢室不可用, 那麼轉會就被推遲:
在單元測試裡對屬性進行mock非常的簡單:
這個測試也會通過的:
遞迴Mock
修改一下IPhysicalExamination介面, 形成一個多層巢狀的屬性:
IPhysicalExamination --> IMedicalRoom --> IMedicalRoomStatus --> IsAvailable.
通過上面這一串來判斷體檢室是否可用.
相應的實現類也要修改:
轉會審批方法裡也要修改:
而在單元測試的方法裡, 肯定是報錯的:
按照正常的思路, 我們可能會這樣做:
就是從內到外一層一層的mock.
這麼做是沒問題的, 測試也會通過:
但是這樣做很麻煩, 而Moq則提供了一種簡單的方式來處理這種多層的/遞迴的mock:
這樣寫即可. 測試同樣會通過:
為屬性設定預設值
但是, 問題來了, 我還有一些其它的單元測試方法, 它們也需要用到這個屬性, 現在它們的狀態是:
有的測試失敗是因為其MockBehavior是Strict的, 而其它的失敗則是因為裡面出現了NullReferenceException.
針對這些情況, 我們可以這樣設定:
這樣設定之後, 它會返回屬性型別的預設值, 因為我沒有設定返回值.
雖然測試依然不通過, 這是因為邏輯上的問題, 而不會丟擲異常:
針對這種情況, 還有一種更好的辦法. 我們可以為mock物件設定預設值:
把DefaultValue的值設為DefaultValue.Mock.
但是DefaultValue這個屬性只對引用型別起作用(對值型別不起作用), 像這種遞迴的mock, 它會遞迴的建立所需的引用型別, 但是最後的IsAvailable這個值型別是不起作用的.
測試:
因為最後一層是bool型別的, 是值型別, 所以上面的設定不起作用, 返回的是false. 所以測試沒通過.
那我就把它改成string型別好了:
審批方法:
然後再除錯測試:
string是引用型別, 但是mock的值依然是null...??!!??
這是因為string是一個sealed class, 而DefaultValue.Mock只對介面, 抽象類和非sealed的class起作用....
不過測試仍然是可以通過的, 因為我改邏輯了:
注意, 這個預設值只對寬鬆(Loose) mock, 起作用.針對Strict mock, 仍然需要設定最後一層屬性的值.
屬性值變化跟蹤
需要新增一些程式碼, 首先新增一個列舉:
為介面新增屬性:
實現類:
然後在審批類裡, 我設定了這個屬性的值:
上面的程式碼也就是說, 我的mock物件的某個屬性在測試的時候它的值會發生變化. 而Moq可以記住這些mock屬性的變化的值.....
新寫一個測試:
這裡使用mockObj.SetupProperty()方法來開始追蹤屬性. 這個測試會通過:
該方法也可以通過下面的寫法來為被追蹤的屬性設定預設值:
mockExamination.SetupProperty(x => x.PhysicalGrade, PhysicalGrade.Failed);.
如果這個物件上有很多屬性需要進行設定和追蹤, 那麼可以使用:
mock.SetupAllProperties(); 這個方法:
注意, 這個方法應該最先呼叫, 否則的話其它的設定可能會被覆蓋.
本文完成的程式碼在: https://github.com/solenovex/Moq4-Tutorial-Code 裡面的03 After.
未完待續......