第一篇文章, 關於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.
未完待續......