为ASP .NET MVC创建unit testing的问题
我正在为我的ASP .NET MVC Controller类创建一些unit testing,我遇到了一些非常奇怪的错误:
我的控制器代码如下:
[HttpPost] [ValidateAntiForgeryToken] public ActionResult Delete(JournalViewModel journal) { var selectedJournal = Mapper.Map(journal); var opStatus = _journalRepository.DeleteJournal(selectedJournal); if (!opStatus.Status) throw new System.Web.Http.HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound)); return RedirectToAction("Index"); }
我的测试代码如下:
[TestMethod] public void Delete_Journal() { // Arrange // Simulate PDF file HttpPostedFileBase mockFile = Mock.Create(); Mock.Arrange(() => mockFile.FileName).Returns("Test.pdf"); Mock.Arrange(() => mockFile.ContentLength).Returns(255); // Create view model to send. JournalViewModel journalViewModel = new JournalViewModel(); journalViewModel.Id = 1; journalViewModel.Title = "Test"; journalViewModel.Description = "TestDesc"; journalViewModel.FileName = "TestFilename.pdf"; journalViewModel.UserId = 1; journalViewModel.File = mockFile; // Add simulated file Mock.Arrange(() => journalRepository.DeleteJournal(null)).Returns(new OperationStatus { Status = true }); // Act PublisherController controller = new PublisherController(journalRepository, membershipRepository); RedirectToRouteResult result = controller.Delete(journalViewModel) as RedirectToRouteResult; // Assert Assert.AreEqual(result.RouteValues["Action"], "Index"); }
问题1 – 映射exception:
每次运行测试时,我都会收到以下exception:
测试名称:Delete_Journal测试
FullName:Journals.Web.Tests.Controllers.PublisherControllerTest.Delete_Journal
测试源:\ Source \ Journals.Web.Tests \ Controllers \ PublisherControllerTest.cs:第132行
测试结果:测试持续时间失败:0:00:00,3822468结果StackTrace:位于\ sources \ Journals.Web \ Controllers \ PublisherController.cs中的Journals.Web.Controllers.PublisherController.Delete(JournalViewModel日志):位于Journals.Web.Tests.Controllers.PublisherControllerTest.Delete_Journal()的第81行\来源\ \ Journals.Web.Tests \ Controllers \ PublisherControllerTest.cs:第156行结果消息:测试方法Journals.Web.Tests.Controllers.PublisherControllerTest.Delete_Journal引发exception:AutoMapper.AutoMapperMappingException:缺少类型映射配置或不支持的映射。
映射类型:JournalViewModel – > Journal Journals.Model.JournalViewModel – > Journals.Model.Journal
目的地路径:期刊
来源价值:Journals.Model.JournalViewModel
似乎JournalViewModel
和Journal
之间存在映射问题,但我不知道它在哪里。 我将此代码添加到Global.asax.cs
的Application_Start
:
Mapper.CreateMap(); Mapper.CreateMap();
从Journal
到JournalViewModel
映射工作正常。
最后我尝试添加Mapper.CreateMap();
作为Delete
方法的第一行,然后一切正常,但我不知道为什么。
问题2 – HTMLexception
使用上面的解决方法运行映射后,我遇到了一个问题,其中属性Status
来自var opStatus = _journalRepository.DeleteJournal(selectedJournal);
总是假的,即使我用Mock覆盖它并使它始终为真。 这会导致抛出不应发生的HTMLexception。
编辑
我在Application_Start中更改为:
Mapper.Initialize(cfg => { cfg.CreateMap(); cfg.CreateMap(); });
但我仍然有同样的错误。
编辑 – 问题2解决了
事实certificate,我忘了将映射添加到我的unit testing类,所以我做了以下事情:
[TestInitialize] public void TestSetup() { // Create necessary mappings Mapper.CreateMap(); Mapper.CreateMap(); //...other code omitted for brevity }
事实certificate,这是问题的根源。 我认为,因为在unit testing中从不调用Global.asax.cs Application_Start(),所以从不创建Mapping,所以我必须在unit testing初始化中自己完成。
问题1
Automapper同时具有静态和实例API 。 您应该考虑将实例API与IMapper
一起IMapper
并将其注入您的控制器。
public class PublisherController : Controller { private readonly IMapper mapper; public PublisherController(IJournalRepository journalRepository, IMembershipRepositry membershipRepository, IMapper mapper) { //...other code omitted for brevity this.mapper = mapper; } //...other code omitted for brevity [HttpPost] [ValidateAntiForgeryToken] public ActionResult Delete(JournalViewModel journal) { var selectedJournal = mapper.Map(journal); var opStatus = _journalRepository.DeleteJournal(selectedJournal); if (!opStatus.Status) throw new System.Web.Http.HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound)); return RedirectToAction("Index"); } }
这将允许根据需要更好地模拟/伪装/配置映射。 您应确保将IMapper
配置为dependency injection到控制器中。
如果您无法更改为实例api,则需要在运行测试之前确保映射器为Initialize
Mapper.Initialize(cfg => { cgf.CreateMap(); });
问题2
你在考试中的安排是
Mock.Arrange(() => journalRepository.DeleteJournal(null)).Returns(new OperationStatus { Status = true });
这是因为您意识到journalRepository.DeleteJournal
用于使用实际实例调用journalRepository.DeleteJournal
的情况。 假设您正在使用来自Telerik的JustMock,您应该安排更灵活的参数。
Mock.Arrange(() => journalRepository.DeleteJournal(Arg.IsAny())).Returns(new OperationStatus { Status = true });
资料来源: JustMock安排中的处理参数
完整测试:实例API
[TestMethod] public void Delete_Journal() { // Arrange //Configure mapping just for this test but something like this //should be in accessible from your composition root and called here. var config = new MapperConfiguration(cfg => { cfg.CreateMap(); cfg.CreateMap(); }); var mapper = config.CreateMapper(); // IMapper // Simulate PDF file var mockFile = Mock.Create(); Mock.Arrange(() => mockFile.FileName).Returns("Test.pdf"); Mock.Arrange(() => mockFile.ContentLength).Returns(255); // Create view model to send. var journalViewModel = new JournalViewModel(); journalViewModel.Id = 1; journalViewModel.Title = "Test"; journalViewModel.Description = "TestDesc"; journalViewModel.FileName = "TestFilename.pdf"; journalViewModel.UserId = 1; journalViewModel.File = mockFile; // Add simulated file var status = new OperationStatus { Status = true }; Mock.Arrange(() => journalRepository.DeleteJournal(Arg.IsAny())).Returns(status); var controller = new PublisherController(journalRepository, membershipRepository, mapper); // Act var result = controller.Delete(journalViewModel) as RedirectToRouteResult; // Assert Assert.AreEqual(result.RouteValues["Action"], "Index"); }
完整测试:静态API
[TestMethod] public void Delete_Journal() { // Arrange //Configure mapping just for this test but something like this //should be in accessible from your composition root and called here. Mapper.Initialize(cfg => { cfg.CreateMap(); cfg.CreateMap(); }); // Simulate PDF file var mockFile = Mock.Create(); Mock.Arrange(() => mockFile.FileName).Returns("Test.pdf"); Mock.Arrange(() => mockFile.ContentLength).Returns(255); // Create view model to send. var journalViewModel = new JournalViewModel(); journalViewModel.Id = 1; journalViewModel.Title = "Test"; journalViewModel.Description = "TestDesc"; journalViewModel.FileName = "TestFilename.pdf"; journalViewModel.UserId = 1; journalViewModel.File = mockFile; // Add simulated file var status = new OperationStatus { Status = true }; Mock.Arrange(() => journalRepository.DeleteJournal(Arg.IsAny())).Returns(status); var controller = new PublisherController(journalRepository, membershipRepository); // Act var result = controller.Delete(journalViewModel) as RedirectToRouteResult; // Assert Assert.AreEqual(result.RouteValues["Action"], "Index"); }