3

我试图理解 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);
    }
}

如果您要为此控制器编写单元测试(获取用户数据),您将在单元测试中测试什么。

谢谢。

4

2 回答 2

7

对于您当前的示例,我无法详细说明单元测试。因为你所得到的只有一个方法ShowDetails,它返回一个View与 绑定的UsersIdentificationViewModel. 由于您只返回View,因此您可以将返回类型更改为ViewResult. 然后,您所能测试的就是与 绑定的模型View是否为 type 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);
    }

    ...
}

为了避免与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();
}  

登录模型.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; }
}

您可以在操作中注意到许多if..else条件,LogOn它们是单元测试的良好候选者。

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

  1. 验证输入有效凭据时,操作应重定向到传递的 url。
  2. 验证是否传递有效凭据,而不returnUrl应将AccountController用户重定向到Home操作。
  3. 验证传递无效凭据时,帐户控制器应返回带有错误的视图。
  4. 验证当存在验证错误时,控制器应返回带有错误的视图。

这是我使用MSTestand编写的单元测试RhinoMocks

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));
   }
}
于 2012-11-13T10:30:26.753 回答
1

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

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

于 2012-11-13T09:33:21.413 回答