LukLed
LukLed

Reputation: 31842

What should be tested in ASP.NET MVC controller's code?

Title is not very specific, but lets use example. We have ASP.NET MVC action code:

[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 });
}

This is simple method. It takes user id and view model of password change form. If model state is valid, it retrieves user from service layer and calls function to check his old password. Administrators don't have to provide user's old password, their own is enough. If password is correct, function to change user's password is called. Appropriate message is placed in TempData in case of success or failure. Action ends with redirection to user edit page, which contains form to change password and displays all errors.

I have few questions:

Interfaces and classes used in code (implementation are injected in constructor, but it doesn't matter):

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" });
    }
}

Upvotes: 2

Views: 270

Answers (1)

VJAI
VJAI

Reputation: 32758

Unit tests should be simple and easy to understand. You should avoid testing lot of scenarios in a single test. Because if some assert fails then you don't know what happens to the following asserts. Unit tests is a kind of documentation. You should break the bigger ones into smaller ones and so you will have a good control in the unit tests.

I've started unit testing very recently especially for the ASP.NET MVC projects. Whenever I see if..else I go for two tests. If I've a controller action like that then the following are the unit tests I'll write.

1. Tests for action results

This is a simple test and in this test I'll check the output action result contains the action name ControllerActions.Edit and the id in route values. Since you are always returning the same action result with same values irrespective of conditions one unit test is enough for this.

2. Tests for Roles

So here I'll write two unit tests one for the admin and the other for the rest. I'll create a mock for the _loginService and set expectations such that when the user is admin then the _loginService is called with the value I set in the CurrentPrincipal.User.UserName. (Is that CurrentPrincipal is a custom object? I'm not sure how you are going to mock it though).

In the non-admin test I'll set the expectation in the mock object such that the user.UserName is passed to the _loginService with the value I expected.

In the later test I've to mock the _userService and stub the GetUserByID method to return a custom user.

3. Tests for correct/incorrect old password

Here I'll write two test cases. If the old password is correct whether I'm getting some value in the TempData also there should not be model errors and the reverse for the other test.

4. Password change sucess/failure test

Here we may need two test cases to test the returned messaged in the TempData when the password is changed successfully or failed due to some exception.

Upvotes: 2

Related Questions