本文介紹了一些新的概念,大部分是基於C#3.0語言特徵。可是C#2.0的用戶沒必要擔憂,由於有着大量吸引他們的東西(例如內聯約束)。可是本文的關注點在於如何在C#3.0中使用Rhino Mocks。html
Rhino Mocks 3.5中的新事物有:正則表達式
一般,我建議遵循「Test only one per test」(每一個測試只測試同樣,測試點單一)。將此應用到Rhino Mocks,每一個單元測試應該只驗證與另外一個對象不超過1個的顯著交互。這就是說,一個給定的測試,不該該有多個模擬對象,可是根據需求,可能會有多個樁。(一個例外的狀況是,一個測試自己就須要兩個以來對象的預期,例如一個對象的方法在另一個對象的方法執行後才能被執行)。ide
你能夠從 Mocks Aren't Stubs這篇文章中獲取這些術語的實際定義。本文只從Rhino Mocks的視角關注兩者的不一樣。單元測試
一個模擬(mock)是一個咱們可以設置預期的對象,並驗證預期的行爲真實發生了(模擬須要預先編寫好預期,造成一個指望接收到的規範調用)。測試
一個樁(stub)是一個用於傳遞給被測試的代碼的對象,你能夠對它設置預期,它將按某種方式執行,但這些預期將不會被驗證。樁的屬性與普通屬性行爲表現同樣,並且不能對它們設置預期(樁是針對調用提供錄製的答案,不會響應測試外的東西)。this
若是想要驗證被測試代碼的行爲,就應該使用設置了合適的預期的模擬對象,並驗證這些預期。若是你只想要傳遞一個值, 它可能會按照某種方式行事,但它不是測試的重點,就應該使用樁。spa
重要提示:樁永遠不會致使測試失敗!翻譯
下面讓咱們建立一個類,它將帶給咱們更多的討論。咱們想要編寫一個對忘記密碼時發送SMS的測試。代碼應該是這樣的:3d
首先,咱們想要測試密碼重置,因此測試代碼是這樣的:code
[Test] public void Test_When_User_Forgot_Password_Should_Reset_Password() { var stubUserRepository = MockRepository.GenerateStub<IUserRepository>(); var stubedSmsSender = MockRepository.GenerateStub<ISmsSender>(); var theUser = new User() {HashedPassword="this is not hashed password"}; var loginController = new LoginController(stubUserRepository, stubedSmsSender); loginController.ForgotMyPassword("ayende"); Assert.AreNotEqual("this is not hashed password",theUser.HashedPassword); }
在這個測試中,咱們建立了兩個樁,並將他們傳給被測試的代碼。爲了使得測試經過,咱們能夠這樣:
public void ForgotMyPassword(string username) { var user = _userRepository.GetUserByName(username); user.HashedPassword = "new pass"; }
如今,咱們想要驗證用戶將會保存密碼, 因而有了下面的測試:
[Test] public void Test_When_User_Forgot_Password_Should_Save_User() { var mockUserRepository = MockRepository.GenerateMock<IUserRepository>(); var mockSmsSender = MockRepository.GenerateMock<ISmsSender>(); var theUser = new User() { HashedPassword = "this is not hashed password" }; //使用存根傳遞被測試對象,並設置預期,預期會按某種方式執行,但不會被覈實。此處使用的是存根方法,預期調用GetUserByName時返回TheUser mockUserRepository.Stub(repository => repository.GetUserByName("ayende")).Return(theUser); //使用模擬對象設置預期,預期模擬對象會執行SaveUser的操做 mockUserRepository.Expect(repository => repository.SaveUser(theUser)); var loginController = new LoginController(mockUserRepository, mockSmsSender); loginController.ForgotMyPassword("ayende"); //模擬對象覈實預期行爲 mockUserRepository.VerifyAllExpectations(); }
咱們如今已經有了一個模擬對象,並對它設置了預期。注意,儘管咱們在這裏使用的是模擬對象,咱們仍然能夠經過stub調用GetUserByName。咱們不須要關注着點,由於這並非咱們測試的重點。
如今,被測試的代碼應該是這樣的:
public void ForgotMyPassword(string username) { var user = _userRepository.GetUserByName(username); user.HashedPassword = "new pass"; _userRepository.SaveUser(user); }
此測試的另一種寫法:
[Test] public void Test_When_User_Forgot_Password_Should_Save_User_Replace() { var stubUserRepository = MockRepository.GenerateStub<IUserRepository>(); var stubSmsSender = MockRepository.GenerateStub<ISmsSender>(); var theUser = new User() { HashedPassword = "this is not hashed password" }; //使用存根傳遞被測試對象,並設置預期,預期會按某種方式執行,但不會被覈實。預期調用GetUserByName時返回TheUser stubUserRepository.Stub(repository => repository.GetUserByName("ayende")).Return(theUser); var loginController = new LoginController(stubUserRepository, stubSmsSender); loginController.ForgotMyPassword("ayende"); //斷言模擬對象會調用指定方法 stubUserRepository.AssertWasCalled(repository => repository.SaveUser(theUser)); }
在上面的測試中,咱們沒有預期,但設置了需求狀態,並斷言測試運行時,代碼會執行該操做。兩個測試的不一樣之處很微妙的,但很重要!在大部分的案例中,應該優先使用樁(stub)。只有在測試複雜的交互時,推薦使用模擬對象。若是你想要確保順序,或者期待獲取一個複雜的方法,或者想要確保確切遵照預期,應該使用模擬對象。
到如今爲止,咱們尚未關心過實際發送一個SMS,如今,咱們來編寫一個測試,確保咱們能發送SMS:
[Test] public void Test_When_User_Forgot_Password_Send_SMS() { var stubUserRepository = MockRepository.GenerateStub<IUserRepository>(); var stubSmsSender = MockRepository.GenerateStub<ISmsSender>(); var theUser = new User() {HashedPassword = "this is not hashed password", Phone = "1234-1234"}; stubUserRepository.Stub(repository => repository.GetUserByName("ayende")).Return(theUser); var loginController = new LoginController(stubUserRepository, stubSmsSender); loginController.ForgotMyPassword("ayende"); stubSmsSender.AssertWasCalled( sender => sender.Send(Arg.Is("1234-1234"), Arg.Text.StartsWith("Password was changed to:"))); }
在這個測試中,咱們斷言方法會被執行,並使用內聯約束支持來確保傳遞了正確的參數。被測試的代碼(ForgotMyPassword)如今應該是這樣的:
public void ForgotMyPassword(string username) { var user = _userRepository.GetUserByName(username); user.HashedPassword = "new pass"; _userRepository.SaveUser(user); _sender.Send(user.Phone, "Password was changed to:" + user.HashedPassword); }
記住:咱們不但願被測試束縛住。這點很重要。咱們但願確保的是:儘管咱們修改了被測試的代碼時,仍然不想破壞其餘無關的測試。
在Rhino Mocks3.4及以前的版本中,獲取模擬對象的默認方式是調用mocks.CreateMock(ISmsSender)。這種方式形成了很是嚴重的問題。CreateMock()方法將返回一個精確模擬(Strict Mock),並且就像咱們已經討論過的,過於嚴格的模擬將使得測試很脆弱。因爲這點,CreateMock()被棄用,而且若是被使用將會在生成一個編譯警告。你能夠用StrictMock()方法替代CreateMock()方法。
然而,不鼓勵使用StrictMock()。若是某些東西在它們身上未能按預期那樣發生,精確模擬(Strict Mock)將會失敗。從長遠來看,這意味着任何對被測試代碼的修改都將致使你的測試失敗,儘管這些改變與你實際正在進行的特定測試無關。
所以,我鼓勵使用樁(stubs)和動態模擬對象(dynamic mock)。
Rhino Mock3.5引入了一種新的模擬方式,在不需建立存儲庫(Repository)的狀況下使用模擬。The purpose of that is to reduce the amount of book keeping that you have to do(啥玩意兒!徹底看不懂)。
這是不使用存儲庫時的測試:
[Test] public void Test_When_User_Forgot_Password_Should_Save_User() { var mockUserRepository = MockRepository.GenerateMock<IUserRepository>(); var stubedSmsSender = MockRepository.GenerateStub<ISmsSender>(); var theUser = new User() {HashedPassword = "this is not hashed password"}; //使用存根傳遞被測試對象,並設置預期,預期會按某種方式執行,但不會被覈實。此處使用的是存根方法,預期調用GetUserByName時返回TheUser mockUserRepository.Stub(repository => repository.GetUserByName("ayende")).Return(theUser); //使用模擬對象設置預期,預期模擬對象會執行SaveUser的操做 mockUserRepository.Expect(repository => repository.SaveUser(theUser)); var loginController = new LoginController(mockUserRepository, stubedSmsSender); loginController.ForgotMyPassword("ayende"); //模擬對象覈實預期行爲 mockUserRepository.VerifyAllExpectations(); }
使用存儲庫時,測試是這樣的:
[Test] public void Test_When_User_Forget_Password_Should_SaveUser_With_Repository() { var mocks = new MockRepository(); var mockUserRepository = mocks.DynamicMock<IUserRepository>(); var stubedSmsSender = mocks.Stub<ISmsSender>(); using (mocks.Record()) { var theUser = new User() {HashedPassword = "this is not hashed password"}; mockUserRepository.Stub(repository => repository.GetUserByName("ayende")).Return(theUser); mockUserRepository.Expect(repository => repository.SaveUser(theUser)); } using (mocks.Playback()) { var loginController = new LoginController(mockUserRepository, stubedSmsSender); loginController.ForgotMyPassword("ayende"); } }
這兩個方法有幾點不一樣:
在Rhino Mock3.5中最重要的功能是AAA語法。Arrange,Act,Assert是一個組織你的測試的經典方式。首先,安排狀態;而後,執行被測試的代碼;最後,斷言你指望的狀態發生改變或行爲觸發了。基於交互的測試,一般是很難進行的,並且還要求使用Record/Play模式。這種案例對基於交互測試的新手來講是很困惑的。AAA語法恰好解決了這個問題。
讓咱們來看一個簡單的測試,並按AAA方式來對它進行分析:
[Test] public void Test_When_User_Forget_Password_Should_SaveUser_With_AAA() { //Arrange var stubedUserRepository = MockRepository.GenerateStub<IUserRepository>(); var stubedSmsSender = MockRepository.GenerateStub<ISmsSender>(); var theUser = new User() {HashedPassword = "this is not hashed password"}; stubedUserRepository.Stub(repository => repository.GetUserByName("ayende")).Return(theUser); //Act var loginController = new LoginController(stubedUserRepository, stubedSmsSender); loginController.ForgotMyPassword("ayende"); //Assert stubedUserRepository.AssertWasCalled(repository => repository.SaveUser(theUser)); }
咱們能夠很容易的看到測試的不一樣階段。這種方法的行爲的主要支持者是Expect()和Stub()方法。
當你引用了Rhino.Mocks名稱空間後,模擬對象可使用Expect()和Stub()擴展方法。Stub()方法對於樁對象也是可用的。
Expect()方法爲模擬對象建立了一個新的預期,這個預期必須在後面被驗證或斷言(使用VerifyAllExpectations()或AssertWasCalled)。
Expect功能相似於以前版本的Rhino Mocks的Expect.Call()功能,Expect()擴展方法可以在沒有Record()塊時使用(Expect.Call()必須在顯式的Record()塊中使用)。
考慮下面的例子的用法,它確保了控制器的SingOut操做調用了mockAuthSvr.SignOut()方法:
[Test] public void Test_Should_SignOut_of_AuthSerive_When_The_LoginOut_Is_Invoked() { var mockAuthSvr = MockRepository.GenerateMock<IAuthService>(); mockAuthSvr.Expect(service => service.SignOut()).Return(true); var signOutController = new SignOutController(mockAuthSvr); signOutController.SignOut(); mockAuthSvr.VerifyAllExpectations(); }
Stub()擴展方法能夠stub一個指定成員,使其能夠執行默認的樁行爲(例如,使測試不失敗。——對於指定行爲請查看本文檔中以前關於stub的定義)。
若是你想要以某種方式影響代碼,就應該使用這種方式。記住:默認的,Rhino Mocks會忽略爲預期的方法調用。下面即是一好的例子:
public void SignOut() { if(_authService.SignOut()) _notificationService.UserIsLoggedOut(); }
下面是測試代碼,咱們樁調用了SignOut並返回true,而後預期調用UserIsLoggedOut()方法:
[Test] public void Test_Should_Notify_When_User_SignOut_Successfully() { //Assert var stubAuthSvr = MockRepository.GenerateStub<IAuthService>(); var mockNotifySvr = MockRepository.GenerateMock<INotificationService>(); stubAuthSvr.Stub(service => service.SignOut()).Return(true); mockNotifySvr.Expect(service => service.UserIsLoggedOut()); //Act var signOutController = new SignOutController(stubAuthSvr, mockNotifySvr); signOutController.SignOut(); //Assert mockNotifySvr.VerifyAllExpectations(); }
另外的一種方式是不顯式的預期方法調用,而是使用斷言指定的方法會被調用:
[Test] public void Test_Should_Notify_When_User_SignOut_Successfully_With_Assert() { //assert var stubAuthSvr = MockRepository.GenerateStub<IAuthService>(); var mockNotifySvr = MockRepository.GenerateMock<INotificationService>(); stubAuthSvr.Stub(service => service.SignOut()).Return(true); //act var signOutController = new SignOutController(stubAuthSvr, mockNotifySvr); signOutController.SignOut(); mockNotifySvr.AssertWasCalled(service => service.UserIsLoggedOut()); }
expect()能夠用來設置預期並返回屬性的值。
下面的例子來自於一個Model-View-Controller項目,測試了Controller的RunView()方法。View中的一個屬性的getter被模擬,並預期它的調用:
public void RunView_Returns_the_View_List { //Arrange List<string> expectedlist = new List<string>(); expectedlist.Add("Ayende"); expectedlist.Add("Tom"); Form Parent = null; //Stub, the parent doesn't matter for this test. IGetNamesView mockview = MockRepository.GenerateMock<IGetNamesView>(); mockview.Expect(view => view.ShowDialog(Parent)).Return(DialogResult.OK); mockview.Expect(view => view.Names).Return(expectedlist); //Mock for the property getter GetNameController target = new GetNameController(Parent, mockview); List<string> actual; //Act actual = target.RunVeiw(); //Assert mockview.VerifyAllExpectations(); Assert.AreEqual(expectedlist, actual); }
在以前版本的Rhino Mocks中,GenrrateMock()等價於MockRepository的實例調用DynamicMock()。GenerateMock()將生成一個動態模擬對象(相對於靜態Mock和Stub)。
在以前版本的Rhino Mocks中,GenrrateStub()等價於MockRepository的實例調用Stub()。GenerateStub會建立一個樁實例,該實例有着樁行爲(查看本文以前關於樁的定義和樁的解釋語義)。
在C#2.0中使用AAA語法也是可行的,只須要咱們理解如何編譯委託擴展方法便可。下面是以前的測試在C#2.0中的寫法:
Test public static void Test_Using_Extension_Methods_Using_2_0() { IAuthService authSvc = MockRepository.GenerateStub(); INotificationService notificationSvc = MockRepository.GenerateMock(); RhinoMocksExtensions.Expect(notificationSvc , delegate(INotificationService o) { o.UserIsLoggingOut(); }); RhinoMocksExtensions.Stub(authSvc, delegate(IAuthService svc) { svc.SignOut(); }).Return(true); SignoutController controller = new SignoutController(authSvc, notificationSvc); controller.SignOut(); RhinoMocksExtensions.VerifyAllExpectations(notificationSvc); } Test public void Test_Using_AAA_Using_2_0() { IAuthService authSvc = MockRepository.GenerateStub(); INotificationService notificationSvc = MockRepository.GenerateMock(); RhinoMocksExtensions.Stub(authSvc, delegate(IAuthService svc) { svc.SignOut(); }).Return(true); SignoutController controller = new SignoutController(authSvc, notificationSvc); controller.SignOut(); RhinoMocksExtensions.AssertWasCalled(notificationSvc, delegate(INotificationService x) { x.UserIsLoggingOut(); }); }
儘管語法不如C#3.0漂亮,但對於C#2.0,咱們仍是有着全部的功能。
參數約束屬於預期調用或樁調用的定義。僅當參數約束匹配時,調用規格纔會生效(這意味着:預期獲得知足,返回值纔會被返回;異常被拋出,Do(指的應該是某個方法)纔會被執行,等等)。若是約束不匹配,它就好像調用了另一個方法。
參數約束還能夠經過指定參數類型來定義方法簽名——這就是爲何它們不能被省略。若是你不指定參數約束,將不能定義預期,由於你不能指定方法簽名。
指定約束的最簡單的方式是直接使用值,就像在示例中常常見到的:
mockUserRepository.Stub(repository => repository.GetUserByName("ayende")).Return(theUser)
字符串值「ayende」是樁規格的一個約束。若是調用GetUserName時使用其餘未指定的參數,將得不到指定的返回值:
[Test] public void Test_Argument_Constraints_With_UnSpecified_Value() { var stubUserRepository = MockRepository.GenerateStub<IUserRepository>(); var theUser = new User() {Name = "ayende", HashedPassword = "this is not hashed password"}; stubUserRepository.Stub(repository => repository.GetUserByName("ayende")).Return(theUser); Assert.IsNull(stubUserRepository.GetUserByName("Ted")); Assert.AreSame(theUser, stubUserRepository.GetUserByName("ayende")); }
你也能夠經過IgnoreArguments()方法來指定任何值都是能夠接受的:
[Test] public void Test_Argument_Constraints_With_Ignore_Arguments() { var stubUserRepository = MockRepository.GenerateStub<IUserRepository>(); var theUser = new User() {Name = "ayende", HashedPassword = "this is not hashed password"}; stubUserRepository.Stub(repository => repository.GetUserByName(null)).IgnoreArguments().Return(theUser); Assert.IsNotNull(stubUserRepository.GetUserByName("Ted")); Assert.AreSame(theUser, stubUserRepository.GetUserByName("ayende")); }
可是,因爲各類緣由,並不鼓勵使用這種「直接使用一個值」的簡單語法。它很是有限。你僅能定義使用Equals進行比較的約束,而對於引用類型一般是進行引用比較。也很容易出現問題,由於給定的值沒有清晰的指定一個類型。例如上述測試中使用了null做爲實例,它沒有指定類型。若是方法被重寫並使用了不一樣類型的參數,你將不能肯定指定的是哪一個方法(你也能夠指定(string)null實例,但這種方式並不直觀)。
一種可替代的方法是使用內聯約束。
舊版本Rhino Mocks的用戶注意:你可能習慣使用IgnoreArguments(),Constraints()和RefOut()。Rhino Mocks3.5引入了新的約束語法,使用Arg<T>,被稱爲內聯約束。你可使用Arg<T>來替代以前的作法。鼓勵僅使用Arg<T>,儘管寫得更多,但它更連貫,也易於理解。
下面是使用內聯依賴來替代以前的例子:
[Test] public void Test_Argument_Constraints_With_Inline_Constraints() { var stubUserRepository = MockRepository.GenerateStub<IUserRepository>(); var theUser = new User() {Name = "ayende", HashedPassword = "this is not hashed password"}; stubUserRepository.Stub(repository => repository.GetUserByName(Arg<string>.Is.Equal("ayende"))) .Return(theUser); Assert.IsNull(stubUserRepository.GetUserByName("stefan")); Assert.AreSame(theUser, stubUserRepository.GetUserByName("ayende")); }
對於首次看到,它可能看起來更長了。你得指定類型,也許你認爲這是沒必要要的。可是請記住,在指定參數類型的同時,還指定了方法簽名。
不只僅限於Equal比較,更多比較引用參見下文。
注意:若是使用了內聯約束,那麼全部的方法參數都應該使用內聯約束來定義。不能與簡單值(plain values)混合使用。
[Test] public void Test_Argument_Constraints_With_Ignoring_Arguments() { var stubUserRepository = MockRepository.GenerateStub<IUserRepository>(); var theUser = new User() {Name = "ayende", HashedPassword = "this is not hashed password"}; stubUserRepository.Stub(repository => repository.GetUserByName(Arg<string>.Is.Anything)) .Return(theUser); Assert.IsNotNull(stubUserRepository.GetUserByName("stefan")); Assert.AreSame(theUser, stubUserRepository.GetUserByName("ayende")); }
Arg.IS<T>(T)實際上Arg<T>.Is.Equal(object)的簡寫,在.NET3.5中,不必指定泛型參數,因而就有了這個更漂亮的簡寫。
stubUserRepository.Stub(repository => repository.GetUserByName(Arg.Is("ayende"))).Return(theUser);
經過將值分配給屬性,你能夠很輕鬆的指定約束。
[Test] public void Test_Property_Constraint() { var mockUser = MockRepository.GenerateMock<User>(); //注意User的屬性必須用virtual修飾 mockUser.Name = "ayende"; mockUser.AssertWasCalled(user => user.Name = Arg<string>.Is.NotNull); }
注意:若是你想要預期屬性被讀取,應該在一個匿名委託內將屬性分配到一個變量。
[Test] public void Tes_Expect_Property_Read() { var mockUser = MockRepository.GenerateMock<User>(); mockUser.Name = "ayende"; var mockUserName = mockUser.Name; //斷言屬性書否被讀取過 mockUser.AssertWasCalled(user => { var ignor = user.Name; }); }
mock.Load += OnLoad;
mock.AssertWasCalled(x => x.Load += Arg<LoadEvent>.Is.Anything);
使用Arg<T>.Matches能夠定複雜的表達式。這個方法有兩個重載,一個容許定義一個lambda表達式,另一個容許使用Rhino Mocks約束定義一個複雜的表達式。
[Test] public void Test_Complex_Expressions_With_Lambda_Expressions() { //arrange var mockUserRepository = MockRepository.GenerateMock<IUserRepository>(); var theUser = new User() {Name = "ayende"}; mockUserRepository.Stub( repository => repository.GetUserByName( Arg<string>.Matches( s => s.StartsWith("aye", StringComparison.InvariantCulture) || s.StartsWith("stein", StringComparison.InvariantCulture)))).Return(theUser); //act and assert Assert.AreSame(theUser, mockUserRepository.GetUserByName("steinegger")); Assert.AreSame(theUser, mockUserRepository.GetUserByName("ayende")); }
[Test] public void Test_Complex_Expressions_With_Constraints() { //arrange var stubUserRepository = MockRepository.GenerateStub<IUserRepository>(); var theUser = new User() {Name = "ayende"}; stubUserRepository.Stub( repository => repository.GetUserByName( Arg<string>.Matches(Rhino.Mocks.Constraints.Text.StartsWith("aye") || Rhino.Mocks.Constraints.Text.StartsWith("stein")))).Return(theUser); //act ,assert Assert.AreSame(theUser, stubUserRepository.GetUserByName("steinegger")); Assert.AreSame(theUser, stubUserRepository.GetUserByName("ayende")); }
Ref和Out參數都很特殊,由於你必須使得編譯器運行順暢。關鍵字ref和out都是受委託的,而且你必須指定一個字段做爲參數。Arg不會讓你失望。
[Test] public void Test_Out_Argument_Constraints() { var stubUserRepository = MockRepository.GenerateStub<IUserRepository>(); User theUser; var user = new User() {Name = "ayende"}; stubUserRepository.Stub( repository => repository.TryGetValue(Arg.Is("ayende"), out Arg<User>.Out(user).Dummy)) .Return(true); var rntvalue = stubUserRepository.TryGetValue("ayende", out theUser); Assert.True(rntvalue); Assert.IsNotNull(theUser); Assert.AreSame(user, theUser); }
對於編譯器,out是受委託的。Arg<User>.Out(user)也是很重要的一部分,它指定了out參數應該返回user。Dummy是一個Arg<T>中T類型的一個字段,用於知足編譯器。
對於ref參數,基本上是相似的:
[Test] public void Test_Ref_Argument_Constraints() { var stubUserRepository = MockRepository.GenerateStub<IUserRepository>(); stubUserRepository.Stub( repository => repository.Foo(ref Arg<string>.Ref(Rhino.Mocks.Constraints.Is.Equal("Stefan"), "Oren").Dummy)) .Return(true); var userName = "Stefan"; bool rntvalue = stubUserRepository.Foo(ref userName); Assert.True(rntvalue); Assert.AreEqual("Oren", userName); }
一樣,ref參數也是受委託的。Arg<string>.Ref(Rhino.Mocks.Constraints.Is.Equal("Stefan"), "Oren")定義了一個約束和一個返回值。一樣,Dummy也是Arg<T>的T類型的一個字段。
Arg<T>.Is | |
Equal(object), NotEqual(object) | 使用Equals比較 |
GreaterThan(object), LessThan(object), LessThanOrEqual(object), GreaterThanOrEqual(object) | 使用>,<,<=,>=操做符比較 |
Same(object), NotSame(object) | 引用比較 |
Anything() | 無約束 |
Null(), NotNull() | 引用爲null或者不爲null |
TypeOf(Type), TypeOf<T>() | 參數爲肯定的類型 |
Matching<T>(Predicate<T> | 參數匹配一個謂詞(詳見Arg<T>.Matches)。 |
IsIn(object) | 枚舉的參數包含指定的對象 |
Arg<T>.List | |
OneOf(IEnumerable) | 參數在指定的列表中 |
Equal(IEnumerable) | 枚舉參數中的全部明細與指定參數中的明細進行比較 |
Count(AbstractConstraint) | 參數的Count屬性的約束 |
Element(int, AbstractConstraint) | 指定的索引處的元素知足約束 |
ContainsAll(IEnumerable) | 枚舉參數至少包含指定的明細 |
Arg<T>.Property | |
AllPropertiesMatch(object) | 全部的公開屬性與公開字段與指定對象的屬性進行比較。若是公開屬性或字段是複雜類型,比較將進行遞歸。 |
IsNotNull(string propertyName), IsNull(string propertyName) | 給定名稱的屬性爲null或不爲null |
Value(string propertyName, object expectedValue) | 給定名稱的屬性等於預期的值 |
Value(string propertyName, AbstractConstraint constraint) | 給定名稱的屬性知足約束 |
Arg.Text | |
StartsWith(string), EndsWith(string), Contains(string) | 字符串的時頭、尾或包含指定的字符串 |
Like(string regex) | 字符串匹配正則表達式 |
Arg<T>.Matches | |
Argt<T>.Matches(Expression) | 經過一個Lambda表達式來指定約束。如: Arg<string>.Matches(s =>s.StartsWith("aye", StringComparison.InvariantCulture)) |
Argt<T>.Matches(AbstractConstraint) | 直接指定約束,如: Arg<string>.Matches(Rhino.Mocks.Constraints.Text.StartsWith("aye")) |
Rhino Mocks3.5引入了一種新的方式來從模擬對象發出事件。在3.5以前,通常都是經過IEventRaiser接口實現的:
[Test] public void RaisingEventOnView() { var mocks = new MockRepository(); IView view = mocks.CreateMock<IView>(); view.Load+=null;//create an expectation that someone will subscribe to this event LastCall.IgnoreArguments();// we don't care who is subscribing IEventRaiser raiseViewEvent = LastCall.GetEventRaiser();//get event raiser for the last event, in this case, View mocks.ReplayAll(); Presenter p = new Presenter(view); raiseViewEvent.Raise(); Assert.IsTrue(p.OnLoadCalled); }
而3.5提供的擴展方法,使得實現這點變得更簡單:
[Test] public void RaisingEventOnViewUsingExtensionMethod() { var mocks = new MockRepository(); IView view = mocks.DynamicMock<IView>(); Presenter p = new Presenter(view); view.Raise(x => x.Load += null, this, EventArgs.Empty); Assert.IsTrue(p.OnLoadCalled); }
Raise()擴展方法傳入一個你想要發出的事件的捐助,事件發送者和事件參數。
Rhino Mocks3.5添加了2個構想用於預期設置屬性。在以前的Rhino Mocks也是能夠的,可是沒有在流接口語法中表現出來。
用一個肯定值來對屬性設置預期:
Expect.Call(mockedCustomer.Name).SetPropertyWithArguments("Ayende");
對於交互測試,不指定值來設置屬性預期時這樣處理:
Expect.Call(mockedCustomer.Name).SetPropertyAndIgnoreArguments();
而用新的構想來處理:
using(mocks.Record()) {
mockedCustomer.Name = "Ayende"; // 設置指定參數來設定一個預期 LastCall.IgnoreArguments(); //針對參數設置的預期,不論實際值是什麼,忽略預期的參數 }
對於一些高級的場合,與其設置預期或斷言特定值,還不如直接處理方法調用來得更容易。
Rhino Mocks經過WhenCalled方法來支持這些功能。WhenCalled可干預模擬行爲,示例以下:
[Test] public void Test_Access_Method_Argument_Directly() { var wasCalled = false; var stub = MockRepository.GenerateStub<IView>(); stub.Stub(view => view.StringAndString(Arg.Is(""))) .Return("blah") .WhenCalled(delegate { wasCalled = true; }); Assert.AreEqual("blah", stub.StringAndString("")); Assert.True(wasCalled); }
上面的例子只展現了在方法調用時獲得通知,實際上WhenCalled有着更強大的功能。來看下面的例子:
[Test] public void Test_Modify_Method_Return_Value_By_WhenCalled() { var stub = MockRepository.GenerateStub<IView>(); stub.Stub(view => view.StringAndString(Arg.Is(""))) .Return("blah") .WhenCalled(invocation => invocation.ReturnValue = "haha"); Assert.AreEqual("haha", stub.StringAndString("")); }
[Test] public void Test_Inspect_Method_Argument_By_WhenCalled() { var stub = MockRepository.GenerateStub<IView>(); stub.Stub(view => view.StringAndString(Arg<string>.Is.Anything)) .Return("blah") .WhenCalled(invocation => Assert.AreNotEqual("", invocation.Arguments[0])); Assert.AreEqual("blah",stub.StringAndString("Stefan")); }
如你所見,WhenCalled方法的功能是很強大的,因此使用時要謹慎。
本文源於我的翻譯,敬請指正!
原文地址:Rhino Mocks 3.5