Reputation: 1181
[TestMethod]
public void Can_Login_With_Valid_Credentials()
{
//Arrange
Mock<IMembershipRepository> mockRepository = new Mock<IMembershipRepository>();
Mock<LoginViewModel> mockModel = new Mock<LoginViewModel>();
mockModel.Setup(x => x.IsLoggedIn()).Returns(true);
AccountController target = new AccountController(mockRepository.Object);
//Act
ActionResult result = target.Login(mockModel.Object);
//Assert
Assert.IsNotInstanceOfType(result, typeof(ViewResult));
}
And ActionResult in the controller
public ActionResult Login(LoginViewModel viewModel)
{
string returnUrl = (string)TempData["ReturnUrl"];
if (ModelState.IsValid)
{
LoginViewModel model = new LoginViewModel(repository, viewModel);
if (model.IsLoggedIn())
{
if (String.IsNullOrEmpty(returnUrl)) return RedirectToAction("Index", "Home");
else return Redirect(returnUrl);
}
else
{
ModelState.AddModelError("Email", "");
ModelState.AddModelError("Password", "");
}
}
return View(viewModel);
}
I'm having problems with mocking model.IsLoggedIn()
in the ActionMethod
, and it is probably because I'm creating a new instance of the viewmodel LoginViewModel
in that ActionMethod
. That is why mockModel.Setup(x => x.IsLoggedIn()).Returns(true);
in the unit test is not caching it because there is a new instance of the class that has that method.
Is there any way i can mock model.IsLoggedIn()
in the ActionMethod and make it return true
?
Upvotes: 0
Views: 2072
Reputation: 1181
Based on the comments above and research, the best way to do that would be by using an existing LoginViewModel instead of creating a new instance of it. Here is the re-factored vision of ActionResult that works with Moq.
public ActionResult Login(LoginViewModel viewModel)
{
string returnUrl = (string)TempData["ReturnUrl"];
if (ModelState.IsValid)
{
if (viewModel.IsLoggedIn(repository))
{
if (String.IsNullOrEmpty(returnUrl)) return RedirectToAction("Index", "Home");
else return Redirect(returnUrl);
}
else
{
ModelState.AddModelError("Email", "");
ModelState.AddModelError("Password", "");
}
}
return View(viewModel);
}
Upvotes: 0
Reputation: 11301
If there is no way to avoid creating new instance of LoginViewModel in the action method, then introduce a factory which does that. Avoid at all costs direct creation of any concrete class - that makes unit testing impossible.
If action method creates an object using the factory, then concrete factory is passed as controller's constructor parameter. That requires IoC container and custom controller factory, which is relatively simple to add to the project.
With this solution in place, unit test actually mocks factory so that mocked factory returns mocked LoginViewModel object (in fact: mocked object that implements ILoginViewModel interface). This is the way in which I do it in all MVC projects and it works perfectly for production code, unit tests and integration tests.
Upvotes: 2