本文介绍了一些新的概念,大部分是基于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