应该在ASP.NET MVC控制器的代码中测试什么?

时间:2012-06-29 23:25:44

标签: asp.net-mvc unit-testing testing

标题不是很具体,但让我们用例子。我们有ASP.NET MVC操作代码:

[HttpPost]
[ExportModelStateToTempData]
public RedirectToRouteResult ChangePassword(int id, UserChangePasswordVM changePassword)
{
    if (ModelState.IsValid)
    {
        var user = _userService.GetUserByID(id);

        //Checking old password. Administrators can change password of every user, 
        //providing his password instead of user's old password.
        bool oldPasswordIsCorrect = _loginService.CheckPassword(
            CurrentPrincipal.IsInRole(CTRoles.IsAdmin) ?
            CurrentPrincipal.User.UserName : user.UserName,
            changePassword.OldPassword);

        if (oldPasswordIsCorrect)
        {
            TempDataWrapper.Message =
                _userService.ChangePassword(user.UserName, changePassword.NewPassword) ?
                CTRes.PasswordChangedSuccessfully : CTRes.ErrorProcessingRequest;
        }
        else
        {
            ModelStateWrapper.AddModelError("ChangePassword.OldPassword",
                CTRes.CurrentPasswordIsNotValid);
        }
    }

    return RedirectToAction(ControllerActions.Edit, new { id });
}

这是一种简单的方法。它需要用户ID和密码更改表单的视图模型。如果模型状态有效,它将从服务层检索用户并调用函数以检查其旧密码。管理员不必提供用户的旧密码,他们自己就足够了。如果密码正确,则调用更改用户密码的功能。如果成功或失败,将在TempData中放置适当的消息。操作以重定向到用户编辑页面结束,其中包含用于更改密码的表单并显示所有错误。

我几乎没有问题:

  • 此代码中应测试哪些内容?
  • 代码中的if语句很少。你会为每个场景写一个测试吗?
  • 你在控制器中测试什么?

代码中使用的接口和类(实现是在构造函数中注入的,但没关系):

public interface IModelStateWrapper
{
    void AddModelError(string name, string error);
    bool IsValid { get; }
}

public interface IUserService
{
    User GetUserByID(int id);
    bool ChangePassword(string userName, string newPassword);
}

public interface ILoginService
{
    bool CheckPassword(string userName, string password);
}

public interface ITempDataWrapper
{
    string Message { get; set; }
}

public class UserChangePasswordVM : IValidatableObject
{
    [DataType(DataType.Password)]
    public string OldPassword { get; set; }

    [DataType(DataType.Password)]
    public string NewPassword { get; set; }

    [DataType(DataType.Password)]
    public string NewPasswordConfirmation { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (string.IsNullOrEmpty(NewPassword))
            yield return new ValidationResult(CTRes.PasswordNotEmpty, new[] { "NewPassword" });

        if (string.IsNullOrEmpty(NewPasswordConfirmation))
            yield return new ValidationResult(CTRes.ConfirmPassword, new[] { "NewPasswordConfirmation" });

        if (NewPassword != null)
            if (!NewPassword.Equals(NewPasswordConfirmation))
                yield return new ValidationResult(CTRes.PasswordsDontMatch, new[] { "NewPasswordConfirmation" });
    }
}

1 个答案:

答案 0 :(得分:2)

单元测试应该简单易懂。您应该避免在单个测试中测试大量场景。因为如果某些断言失败,那么您不知道以下断言会发生什么。单元测试是一种文档。你应该把较大的那些分成小的,这样你就可以很好地控制单元测试。

我最近开始进行单元测试,尤其是ASP.NET MVC项目。每当我看到...我会去做两次测试。如果我有这样的控制器动作,那么以下是我将要编写的单元测试。

<强> 1。行动结果测试

这是一个简单的测试,在此测试中,我将检查输出操作结果是否包含操作名称ControllerActions.Edit和路由值中的id。由于无论条件如何,总是以相同的值返回相同的动作结果,因此单元测试就足够了。

<强> 2。角色测试

所以在这里,我将为admin编写两个单元测试,其余为其余部分。我将为_loginService创建一个模拟,并设置期望,以便当用户为admin时,_loginService将使用我在CurrentPrincipal.User.UserName中设置的值进行调用。 (那CurrentPrincipal是一个自定义对象吗?我不确定你是怎么去嘲笑它的。)

在非管理员测试中,我将在模拟对象中设置期望值,以便user.UserName以{I}值传递给_loginService

在后面的测试中,我要模拟_userService并存根GetUserByID方法以返回自定义用户。

第3。测试正确/错误的旧密码

在这里,我将编写两个测试用例。如果旧密码是正确的,我是否在TempData中获得了一些值,那么应该没有模型错误,而另一个测试则反之亦然。

<强> 4。密码更改成功/失败测试

这里我们可能需要两个测试用例,以便在密码成功更改或由于某些例外而失败时在TempData中测试返回的messaged。