I am trying to understand the concept of TDD in MVC 4 application. All examples I've found over the net doesn't explained me the concept of what Unit test in a case of MVC 4 application should test.
So when you writing your unit testing , what are the main points you are trying to test in your controller?
If possible please explain on this example.
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);
}
}
If you'd be writing unit test for this controller (gettin user data) what will you test in unit test.
Thanks.
I can't elaborate much about unit testing on the current example you have. Because all you got there is a single method ShowDetails
that returns a View
tied with UsersIdentificationViewModel
. Since you are returning only View
you could change the return type into ViewResult
. Then, all you can test is if the model tied with the View
is of type UsersIdentificationViewModel
.
I could explain unit testing in MVC little more better if we take a simple example. If you create a fresh MVC application selecting the internet template
you would see an AccountController
is defined as default and it contains actions for login, register, change password etc.
Let's take the LogOn
action in the 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);
}
...
}
To avoid the dependency with FormsAuthentication
sealed class with the AccountController
I've used an interface IAuthProvider
to simplify the unit testing.
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; }
}
You can notice many if..else conditions in the LogOn
action and they are good candiates for unit testing.
I would write atleast four unit tests for the action.
returnUrl
the AccountController
should redirect the user to the Home
action.Here are the unit tests I've written using MSTest
and 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));
}
}