Cannot mock a method call in a ASP.NET MVC Controller using Moq
I have the following code in a "UserController" within my ASP.NET MVC application:
public class UserController : Controller
{
public ActionResult Index()
{
return RedirectToAction("List");
}
public ActionResult List()
{
IUserRepository repo = new UserRepository();
IQueryable<Business.Entities.User> users = repo.GetAll();
return View("List", users);
}
}
Using Moq, I would like to mock the database call "repo.GetAll()". Here is what I have for my test:
[Test]
public void List()
{
Mock<IUserRepository> mockRepo = new Mock<IUserRepository>();
mockRepo.Setup(ur => ur.GetAll()).Returns(MockedGetAll());
var v = mockRepo.Object.GetAll();
var controller = new UserController();
var result = controller.List() as ViewResult;
var model = result.ViewData.Model as IQueryable<User>;
Assert.AreEqual("List", result.ViewName);
Assert.IsNotNull(model);
Assert.Greater(model.Count(), 0);
}
I also have a function that would return some static data to satisfy the test:
private IQueryable<User> MockedGetAll()
{
List<User> users =开发者_StackOverflow new List<User>();
users.Add(new User(1, "mark.paterson", "mark.paterson@yahoo.com", "Mark", "Paterson", false, true));
users.Add(new User(2, "nikki.paterson", "nikki.paterson@yahoo.com", "Nikki", "Paterson", false, true));
return users.AsQueryable();
}
The test breaks at "Assert.Greater". I get a 0 records instead of 2. When I debugged the code, the code actually returns the result of the database call, which is supposed to be 0 records, instead of the mocked data.
The following line is killing everything and introducing an impossible to unit test in isolation strong coupling between your controller and data access layer:
IUserRepository repo = new UserRepository();
Absolutely never write anything like this in any application (not only ASP.NET MVC). No matter what you do if you write code like this it will always break in your unit test and you will not be able to test it.
This is impossible to mock/unit test.
You should use constructor injection to weaken coupling between your layers:
public class UserController : Controller
{
private readonly IUserRepository _repo;
public UserController(IUserRepository repo)
{
_repo = repo;
}
public ActionResult Index()
{
return RedirectToAction("List");
}
public ActionResult List()
{
IQueryable<Business.Entities.User> users = _repo.GetAll();
return View("List", users);
}
}
Now you can unit test in isolation by mocking:
[Test]
public void List()
{
Mock<IUserRepository> mockRepo = new Mock<IUserRepository>();
mockRepo.Setup(ur => ur.GetAll()).Returns(MockedGetAll());
var v = mockRepo.Object.GetAll();
var controller = new UserController(mockRepo.Object);
var result = controller.List() as ViewResult;
var model = result.ViewData.Model as IQueryable<User>;
Assert.AreEqual("List", result.ViewName);
Assert.IsNotNull(model);
Assert.Greater(model.Count(), 0);
}
Obviously because now your controller depends on this repository you could use a DI framework to configure your dependencies.
People that don't want to use DI framework often write code like this and provide 2 constructors (one for unit testing and one for the real application):
private readonly IUserRepository _repo;
public UserController(IUserRepository repo)
{
_repo = repo;
}
public UserController(): this(new UserRepository())
{
}
I am providing this in order to illustrate another thing that you should absolutely never do and to stress on the fact that this is poor man's DI.
Haacked also discussed about those issues in his blog post.
精彩评论