使用Fakes的Stub和Shim对ASP.NET MVC4进行单元测试
这是一篇导航文,不是翻译。
MSDN对stub和shim的解释和使用场景演示:http://msdn.microsoft.com/en-us/library/hh549175.aspx
一个更详细的示例:http://www.richonsoftware.com/post/2012/04/05/Using-Stubs-and-Shim-to-Test-with-Microsoft-Fakes-in-Visual-Studio-11.aspx
我感兴趣的是如何对mvc项目进行测试:http://www.richonsoftware.com/post/2012/05/02/Noninvasive-Unit-Testing-in-ASPNET-MVC-A-Microsoft-Fakes-Deep-Dive.aspx
里面讲的内容分别有:
1,利用shim来重写第三方类库或系统类库的方法
[TestMethod] public void TestLogOff() { var accountController = new AccountController(); var formsAuthenticationSignOutCalled = false; RedirectToRouteResult redirectToRouteResult; //Scope the detours we're creating using (ShimsContext.Create()) { //Detours FormsAuthentication.SignOut() to an empty implementation ShimFormsAuthentication.SignOut = () => { //Set a boolean to identify that we actually got here formsAuthenticationSignOutCalled = true; }; redirectToRouteResult = accountController.LogOff() as RedirectToRouteResult; Assert.AreEqual(true, formsAuthenticationSignOutCalled); } Assert.NotNull(redirectToRouteResult); Assert.AreEqual("Index", redirectToRouteResult.RouteValues["Action"]); Assert.AreEqual("Home", redirectToRouteResult.RouteValues["controller"]); }
2,怎么判断该用stub
[TestMethod] public void TestLogin() { string testUserName = "TestUserName"; string testPassword = "TestPassword"; bool testRememberMe = false; string returnUrl = "/foo.html"; var loginModel = new LoginModel { UserName = testUserName, Password = testPassword, RememberMe = testRememberMe }; var accountController = new AccountController(); //Setup underpinning via stubbing such that UrlHelper //can validate that our "foo.html" is local var stubHttpContext = new StubHttpContextBase(); var stubHttpRequestBase = new StubHttpRequestBase(); stubHttpContext.RequestGet = () => stubHttpRequestBase; var requestContext = new RequestContext(stubHttpContext, new RouteData()); accountController.Url = new UrlHelper(requestContext); RedirectResult redirectResult; //Scope the detours we're creating using (ShimsContext.Create()) { //Sets up a detour for Membership.ValidateUser to our mocked implementation ShimMembership.ValidateUserStringString = (userName, password) => { Assert.AreEqual(testUserName, userName); Assert.AreEqual(testPassword, password); return true; }; //Sets up a detour for FormsAuthentication.SetAuthCookie to our mocked implementation ShimFormsAuthentication.SetAuthCookieStringBoolean = (userName, rememberMe) => { Assert.AreEqual(testUserName, userName); Assert.AreEqual(testRememberMe, rememberMe); }; redirectResult = accountController.Login(loginModel, returnUrl) as RedirectResult; } Assert.NotNull(redirectResult); Assert.AreEqual(redirectResult.Url, returnUrl); }
3,如何还原动态对象
[TestMethod] public void TestInvalidJsonLogin() { string testUserName = "TestUserName"; string testPassword = "TestPassword"; bool testRememberMe = false; string testReturnUrl = "TestReturnUrl"; var loginModel = new LoginModel { UserName = testUserName, Password = testPassword, RememberMe = testRememberMe }; var accountController = new AccountController(); JsonResult jsonResult; //Scope the detours we're creating using (ShimsContext.Create()) { //Sets up a detour for Membership.ValidateUser to our mocked implementation ShimMembership.ValidateUserStringString = (userName, password) => false; jsonResult = accountController.JsonLogin(loginModel, testReturnUrl); } //答案在这里 var errors = (IEnumerable<string>)(new PrivateObject(jsonResult.Data, "errors")).Target; Assert.AreEqual("The user name or password provided is incorrect.", errors.First());
4,如何给请求添加上下文,及如何在请求中加入用户对象
[TestMethod] public void TestChangePassword() { string testUserName = "TestUserName"; string testOldPassword = "TestOldPassword"; string testNewPassword = "TestNewPassword"; var changePasswordModel = new ChangePasswordModel { OldPassword = testOldPassword, NewPassword = testNewPassword }; var accountController = new AccountController(); //Stub HttpContext var stubHttpContext = new StubHttpContextBase(); //Setup ControllerContext so AccountController will use our stubHttpContext accountController.ControllerContext = new ControllerContext(stubHttpContext, new RouteData(), accountController); //Stub IPrincipal var principal = new StubIPrincipal(); principal.IdentityGet = () => { var identity = new StubIIdentity { NameGet = () => testUserName }; return identity; }; stubHttpContext.UserGet = () => principal; RedirectToRouteResult redirectToRouteResult; //Scope the detours we're creating using (ShimsContext.Create()) { ShimMembership.GetUserStringBoolean = (identityName, userIsOnline) => { Assert.AreEqual(testUserName, identityName); Assert.AreEqual(true, userIsOnline); var memberShipUser = new ShimMembershipUser(); //Sets up a detour for MemberShipUser.ChangePassword to our mocked implementation memberShipUser.ChangePasswordStringString = (oldPassword, newPassword) => { Assert.AreEqual(testOldPassword, oldPassword); Assert.AreEqual(testNewPassword, newPassword); return true; }; return memberShipUser; }; var actionResult = accountController.ChangePassword(changePasswordModel); Assert.IsInstanceOf(typeof(RedirectToRouteResult), actionResult); redirectToRouteResult = actionResult as RedirectToRouteResult; } Assert.NotNull(redirectToRouteResult); Assert.AreEqual("ChangePasswordSuccess", redirectToRouteResult.RouteValues["Action"]); }
上面种种,文章给的不仅仅是答案,更有思考过程,比如问题原因,可能的解决方法,以及最后给一个最优的方法,值得一读的。