So I want to learn a little bit of TDD and wanted to see if I could test an Index action which should simply return a View.
The test doesn't pass an开发者_JAVA百科d the error is
Test method Summumnet.Tests.Controllers.PhysicalTestsControllerTest.IndexShouldReturnView threw exception: System.ArgumentException: Expression is not a property access: c => c.FindById(1)
Here's my controller action code:
[Authorize]
[AllowedToEditEHR]
public class PhysicalTestsController : Controller
{
private IUnitOfWork unitOfWork;
private IRepository<EHR> ehrRepository;
private const int PAGESIZE = 5;
public PhysicalTestsController(IUnitOfWork unit)
{
unitOfWork = unit;
ehrRepository = unitOfWork.EHRs;
}
public ActionResult Index(int ehrId, int? page)
{
EHR ehr = ehrRepository.FindById(ehrId);
var physicaltests = ehr.PhysicalTests.Where(test => !test.IsDeleted).OrderByDescending(test => test.CreationDate);
List<PhysicalTestListItem> physicalTestsVM = new List<PhysicalTestListItem>();
Mapper.Map(physicaltests, physicalTestsVM);
var paginatedTests = physicalTestsVM.ToPagedList(page ?? 0, PAGESIZE);
return View(paginatedTests);
}
and here's the test
[TestClass]
public class PhysicalTestsControllerTest
{
[TestMethod]
public void IndexShouldReturnView()
{
// Arrange
var mock = new Mock<ControllerContext>();
mock.SetupGet(p => p.HttpContext.User.Identity.Name).Returns("nacho");
mock.SetupGet(p => p.HttpContext.Request.IsAuthenticated).Returns(true);
var mockUnitofWork = new Mock<IUnitOfWork>();
var mockEhrRepository = new Mock<IRepository<EHR>>();
mockEhrRepository.SetupGet(c => c.FindById(1)).Returns(new EHR { PhysicalTests = new List<PhysicalTest>()});
mockUnitofWork.SetupGet(p=>p.EHRs).Returns(mockEhrRepository.Object);
PhysicalTestsController controller = new PhysicalTestsController(mockUnitofWork.Object);
controller.ControllerContext = mock.Object;
// Act
ViewResult result = controller.Index(1, 0) as ViewResult;
// Assert
Assert.IsNotNull(result);
}
A few pointers:
You don't need to mock
HttpContext
for this test. Sure, you have anAuthorize
attribute on your controller, but action filters aren't run when you're unit testing your controllers. So you can just assume that the user will be authenticated and authorized.SetupGet
is, I believe, pretty obsolete. You should consider usingSetup
instead:mockUnitofWork.Setup(p => p.EHRs).Returns(mockEhrRepository.Object);
THis is in fact what is causing your error, since
SetupGet
can only be used on properties, whileSetup
can be used on more or less anything. SinceFindById
isn't a property (it's a method), you get that error.When setting up a function call, you usually get better results if you don't give exact input arguments, but rather specify conditions that the input arguments must satisfy. This avoids unexpected and often hard-to-track testing bugs when you pass in two identical - but separate - instances of a class, and expect them to be the same.
In Moq, you can use the static methods on
It
to do this. If you want to allow for anyint
, just sayIt.IsAny<int>()
in place of the integer argument. If you want to be specific, you can use a lambda expression to declare when the behavior should apply, usingIt.Is<int>(i => someCondition(i))
. So instead of.Setup(c => c.FindById(1))
you saymockEhrRepository.Setup(r => r.FindById(It.Is<int>(i => i == 1)).Returns(...)
For value types this doesn't make any difference, but I think it's a good habit. I usually choose to not care which integer is given, which simplifies the setup expression a little:
mockEhrRepository.Setup(r => r.FindById(It.IsAny<int>()).Returns(...)
But to be clear: The only thing in this post you need to do to get rid of your error, is to change SetupGet
to Setup
.
精彩评论