Mvc 4单元测试概念

时间:2012-11-13 08:43:40

标签: asp.net-mvc-3 unit-testing asp.net-mvc-4 theory

我试图理解MVC 4应用程序中TDD的概念。 我在网上找到的所有例子都没有向我解释在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);
    }
}

如果你要为这个控制器(gettin用户数据)编写单元测试,你将在单元测试中测试什么。

感谢。

2 个答案:

答案 0 :(得分:7)

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

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

我们在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);
    }

    ...
}

为了避免FormsAuthentication使用AccountController密封类的依赖,我使用了接口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 条件,它们是单元测试的良好候选者。

我会为行动写下至少四个单元测试。

  1. 验证输入有效凭据时,操作应重定向到已传递的网址。
  2. 验证在没有returnUrl的情况下传递有效凭据AccountController应将用户重定向到Home操作。
  3. 验证传递无效凭据时,帐户控制器应返回包含错误的视图。
  4. 验证何时出现验证错误,控制器应返回有错误的视图。
  5. 以下是我使用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));
       }
    }
    

答案 1 :(得分:1)

如果您测试ShowDetails方法,您可以验证它是否返回具有正确类型的模型的视图。

为此,您可以使用MVC Contrib,它可以使用单元测试断言。您可以在此处找到所有单元测试文档:http://mvccontrib.codeplex.com/wikipage?title=TestHelper&referringTitle=Documentation