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:
- What should be tested in this code?
- There is few if statements in code. Would you write a test for every scenario?
- What do you test in your controllers?
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" });
}
}