这个TestMethod出了什么问题?

nac*_*10f 2 tdd asp.net-mvc unit-testing

所以我想学习一点TDD并想看看我是否可以测试一个应该只返回一个View的Index动作.

测试没有通过,错误是

测试方法Summumnet.Tests.Controllers.PhysicalTestsControllerTest.IndexShouldReturnView抛出异常:System.ArgumentException:Expression不是属性访问:c => c.FindById(1)

这是我的控制器动作代码:

[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);
        }
Run Code Online (Sandbox Code Playgroud)

这是测试

[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);
        }
Run Code Online (Sandbox Code Playgroud)

Tom*_*han 8

几点建议:

  1. 你不需要模拟HttpContext这个测试.当然,您Authorize的控制器上有一个属性,但是当您对控制器进行单元测试时,不会运行操作过滤器.因此,您可以假设用户将经过身份验证和授权.

  2. SetupGet我相信,它已经过时了.您应该考虑使用Setup:

    mockUnitofWork.Setup(p => p.EHRs).Returns(mockEhrRepository.Object);
    
    Run Code Online (Sandbox Code Playgroud)

    这实际上是导致错误的原因,因为SetupGet它只能在属性上使用,而Setup在或多或少可以使用.既然FindById不是属性(它是一个方法),你会得到那个错误.

  3. 设置函数调用时,如果不提供精确的输入参数,通常会获得更好的结果,而是指定输入参数必须满足的条件.当你传入一个类的两个完全相同但又相互独立的实例时,这可以避免意外且经常难以跟踪的测试错误,并期望它们是相同的.

    在Moq中,您可以使用静态方法It来执行此操作.如果你想允许任何int,只需说It.IsAny<int>()一下整数参数.如果您想要具体,可以使用lambda表达式来声明何时应该应用该行为It.Is<int>(i => someCondition(i)).所以不要.Setup(c => c.FindById(1))你说

    mockEhrRepository.Setup(r => r.FindById(It.Is<int>(i => i == 1)).Returns(...)
    
    Run Code Online (Sandbox Code Playgroud)

    对于价值类型,这没有任何区别,但我认为这是一个好习惯.我通常选择不关心给出哪个整数,这简化了设置表达式:

    mockEhrRepository.Setup(r => r.FindById(It.IsAny<int>()).Returns(...)
    
    Run Code Online (Sandbox Code Playgroud)

但要明确一点:在这个岗位,你唯一需要做摆脱你的错误,就是改变SetupGetSetup.

  • 我绝对更喜欢使用Moq来欺骗假类,主要有两个原因:1)假类可能变得非常复杂,很快你就必须为你的假货编写测试... 2)如果你模拟一个界面,你可以改变在一次测试中该接口的预期行为,而不必担心打破其他测试.使用测试类,您可能必须编写另一种方法,或者使现有方法更复杂,并且突然您再次处于1)...(由您来测试IUnitofWork的实现,因为例如,确保您的设置与真实班级一致). (2认同)