为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

似乎JournalViewModelJournal之间存在映射问题,但我不知道它在哪里。 我将此代码添加到Global.asax.csApplication_Start

 Mapper.CreateMap(); Mapper.CreateMap(); 

JournalJournalViewModel映射工作正常。

最后我尝试添加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"); }