2012-11-13 85 views
3

我想了解在MVC 4应用程序中TDD的概念。 我在网上找到的所有例子都没有向我解释MVC 4应用程序的单元测试应该测试的概念。Mvc 4单元测试概念

所以,当你编写单元测试时,你试图在你的控制器中测试的要点是什么?

如果可能的话请解释一下这个例子。

public class UsersIDentController : AuthorizedController 
{  
    private readonly IUserDetailsService userDetailsService; 

    public UsersIDentController(IUserDetailsService userDetailsService, 
      IServiceLocator serviceLocator): base(serviceLocator) 
    { 
    }  

    // 
    // GET: /UsersIdentification/ 
    [AllowAnonymous] 
    public ActionResult ShowDetails() 
    { 
     UsersIdentificationViewModel model = new UsersIdentificationViewModel(); 
     return View(model); 
    } 
} 

如果您要为此控制器编写单元测试(请参阅用户数据),您将在单元测试中测试什么?

谢谢。

回答

7

我不能在当前的例子中详细说明单元测试。因为你所得到的只有一个方法ShowDetails,它会返回ViewUsersIdentificationViewModel并列。由于您仅返回View,因此您可以将退货类型更改为ViewResult。然后,您可以测试的是与View绑定的型号是否为UsersIdentificationViewModel

如果我们举一个简单的例子,我可以更好地解释MVC中的单元测试。如果您创建一个新的MVC应用程序选择internet template你会看到一个AccountController被定义为默认值,它包含登录寄存器修改密码的操作

让我们以LogOn行动在AccountController

AccountController.cs

public class AccountController : Controller 
{ 
    private readonly IAuthProvider _authProvider; 

    public AccountController(IAuthProvider authProvider) 
    { 
     _authProvider = authProvider; 
    } 

    [HttpPost] 
    public ActionResult LogOn(LogOnModel model, string returnUrl) 
    { 
     if (ModelState.IsValid) 
     { 
      if (_authProvider.ValidateUser(model.UserName, model.Password)) 
      { 
       _authProvider.SetCookie(model.UserName, model.RememberMe); 

       if (!String.IsNullOrEmpty(returnUrl)) 
       { 
        return Redirect(returnUrl); 
       } 
       else 
       { 
        return RedirectToAction("Index", "Home"); 
       } 
      } 
      else 
      { 
       ModelState.AddModelError("", "The user name or password provided is incorrect."); 
      } 
     } 

     return View(model); 
    } 

    ... 
} 

为了避免与FormsAuthenticationAccountController密封类的依赖我已经使用的接口IAuthProvider简化单元测试。

IAuthProvider.cs

public interface IAuthProvider 
{ 
    bool ValidateUser(string username, string password); 

    void SetCookie(string username, bool rememberMe); 

    bool CreateUser(string username, string password, string email, out string error); 

    bool ChangePassword(string username, string oldPassword, string newPassword); 

    void SignOut(); 
} 

LogOnModel.cs

public class LogOnModel 
{ 
    [Required] 
    [Display(Name = "User name")] 
    public string UserName { get; set; } 

    [Required] 
    [DataType(DataType.Password)] 
    [Display(Name = "Password")] 
    public string Password { get; set; } 

    [Display(Name = "Remember me?")] 
    public bool RememberMe { get; set; } 
} 

您可以注意到在LogOn动作很多的if..else条件,他们是单位好candiates测试。

我会为这个动作写至少四个单元测试。

  1. 验证输入有效凭证的动作应该重定向到传递的URL。
  2. 验证通过有效凭证没有returnUrlAccountController应该将用户重定向到Home操作。
  3. 验证传递无效凭证时,帐户控制器应返回错误视图。
  4. 验证何时出现验证错误,控制器应返回错误视图。

下面是我用MSTestRhinoMocks编写的单元测试。

AccountControllerTests.cs

[TestClass] 
public class AccountControllerTests 
{ 
    private AccountController _accountController; 
    private IAuthProvider _mockAuthProvider; 

    [TestInitialize] 
    public void SetUp() 
    { 
     //** Arrange 
     _mockAuthProvider = MockRepository.GenerateStub<IAuthProvider>(); 
     _accountController = new AccountController(_mockAuthProvider); 
    } 

    [TestCleanup] 
    public void CleanUp() 
    {  
    } 

    /// <summary> 
    /// This test is to verify on entering valid credentials the action should redirect to the passed url. 
    /// </summary> 
    [TestMethod] 
    public void LogOn_Action_Valid_Credentials_With_ReturnUrl_Test() 
    { 
     //** Arrange 
     var logonModel = new LogOnModel 
     { 
      UserName = "trigent", 
      Password = "password", 
      RememberMe = true 
     }; 

     // stub the ValidateUser to return "true" to pretend the user is valid. 
     _mockAuthProvider.Stub(x => x.ValidateUser(logonModel.UserName, logonModel.Password)).Return(true); 

     //** Act 
     var actual = _accountController.LogOn(logonModel, "/"); 

     //** Assert 

     // verify RedirectResult is returned from action 
     Assert.IsInstanceOfType(actual, typeof(RedirectResult)); 

     // verify the redirect url is same as the passed one. 
     Assert.AreEqual(((RedirectResult)actual).Url, "/"); 
    } 

    /// <summary> 
    /// This test is to verify on passing valid credentials without returnUrl the account controller 
    /// should redirect the user to the "Home" action. 
    /// </summary> 
    [TestMethod] 
    public void LogOn_Action_Valid_Credentials_Without_ReturnUrl_Test() 
    { 
     //** Arrange 
     var logonModel = new LogOnModel 
     { 
      UserName = "trigent", 
      Password = "password", 
      RememberMe = true 
     }; 

     // stub the ValidateUser to return "true" to pretend the user is valid. 
     _mockAuthProvider.Stub(x => x.ValidateUser(logonModel.UserName, logonModel.Password)).Return(true); 

     //** Act 
     var actual = _accountController.LogOn(logonModel, string.Empty); 

     //** Assert 

     // verify RedirectToRouteResult is returned from action 
     Assert.IsInstanceOfType(actual, typeof(RedirectToRouteResult)); 

     // verify the controller redirecting to "Home" action. 
     var routeValues = ((RedirectToRouteResult)actual).RouteValues; 
     Assert.AreEqual("Home", routeValues["controller"].ToString()); 
     Assert.AreEqual("Index", routeValues["action"].ToString()); 
    } 

    /// <summary> 
    /// This test is to verify on passing invalid credentials the account controller should return the login view 
    /// with error messages. 
    /// </summary> 
    [TestMethod] 
    public void LogOn_Action_Invalid_Credentials_Test() 
    { 
     //** Arrange 
     var logonModel = new LogOnModel 
     { 
      UserName = "trigent", 
      Password = "password", 
      RememberMe = true 
     }; 

     // stub the ValidateUser to return "false" to pretend the user is invalid. 
     _mockAuthProvider.Stub(x => x.ValidateUser(logonModel.UserName, logonModel.Password)).Return(false); 

     //** Act 
     var actual = _accountController.LogOn(logonModel, string.Empty); 

     //** Assert 

     // verify ViewResult is returned from action 
     Assert.IsInstanceOfType(actual, typeof(ViewResult)); 

     // verify the controller throws error. 
     var modelStateErrors = _accountController.ModelState[""].Errors; 
     Assert.IsTrue(modelStateErrors.Count > 0); 
     Assert.AreEqual("The user name or password provided is incorrect.", modelStateErrors[0].ErrorMessage); 
    } 

    /// <summary> 
    /// This test is to verify when there is a validation error the controller should return the same login view. 
    /// </summary> 
    [TestMethod] 
    public void LogOn_Action_Invalid_Input_Test() 
    { 
     //** Arrange 
     _accountController.ModelState.AddModelError("UserName", "UserName is Required."); 

     //** Act 
     var actual = _accountController.LogOn(new LogOnModel(), string.Empty); 

     //** Assert 

     // verify ViewResult is returned from action 
     Assert.IsInstanceOfType(actual, typeof(ViewResult)); 
    } 
}