为asp.net MVC创建单元测试的问题

本文关键字:单元测试 问题 创建 MVC asp net | 更新日期: 2023-09-27 18:11:37

我正在为我的asp.net MVC控制器类创建一些单元测试,我遇到了一些非常奇怪的错误:

我的控制器代码如下:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Delete(JournalViewModel journal)
{
    var selectedJournal = Mapper.Map<JournalViewModel, Journal>(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<HttpPostedFileBase>();
    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 -映射异常:

每次我运行我的测试,我收到以下异常:

测试名称:Delete_Journal Test
FullName: Journals.Web.Tests.Controllers.PublisherControllerTest.Delete_Journal
测试源代码:'Source' journal . web . tests 'Controllers'PublisherControllerTest.cs:第132行
Test result: Failed Test Duration: 0:00:00,3822468

Result StackTrace: atJournals.Web.Controllers.PublisherController.Delete (JournalViewModel日报》)'Source' journal . web 'Controllers'PublisherController.cs:line 81 atJournals.Web.Tests.Controllers.PublisherControllerTest.Delete_Journal ()在' ' Journals.Web.Tests '源控制器' PublisherControllerTest.cs:行156结果提示:测试方法Journals.Web.Tests.Controllers.PublisherControllerTest.Delete_Journal抛出异常:AutoMapper。AutoMapperMappingException:缺失类型映射配置或不支持的映射。

映射类型:JournalViewModel -> Journaljournal . model . journalviewmodel -> journal . model . journal

目标路径:Journal

源值:journal . model . journalviewmodel

似乎有类JournalViewModelJournal之间的映射问题,但我不知道在哪里。我将此代码添加到Global.asax.cs中的Application_Start:

Mapper.CreateMap<Journal, JournalViewModel>();
Mapper.CreateMap<JournalViewModel, Journal>();

JournalJournalViewModel的映射工作。

最后我尝试添加Mapper.CreateMap<JournalViewModel, Journal>();作为Delete方法的第一行,然后一切都有效,但我不知道为什么。

问题2 - HTML异常

一旦映射与上面的解决方案一起运行,我就会遇到一个问题,即var opStatus = _journalRepository.DeleteJournal(selectedJournal);的属性Status始终为假,即使我使用Mock覆盖它并使其始终为真。这会导致抛出一个本不应该发生的HTML异常。

编辑

我把Application_Start改成:

Mapper.Initialize(cfg =>
{
    cfg.CreateMap<Journal, JournalViewModel>();
    cfg.CreateMap<JournalViewModel, Journal>();
});

但是我还是有同样的错误。

EDIT - Problem 2 Solved

结果是我忘记将映射添加到单元测试类中,所以我做了以下操作:
[TestInitialize]
public void TestSetup()
{
    // Create necessary mappings
    Mapper.CreateMap<Journal, JournalViewModel>();
    Mapper.CreateMap<JournalViewModel, Journal>();
    //...other code omitted for brevity
}

事实证明这就是问题的根源。我认为,由于Global.asax.cs Application_Start()从未在单元测试中调用,因此从未创建映射,因此我必须在单元测试初始化中自己执行此操作。

为asp.net MVC创建单元测试的问题

问题1

Automapper有静态和实例API。你应该考虑使用IMapper的实例API,并将其注入到你的控制器中。

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<JournalViewModel, Journal>(journal);
        var opStatus = _journalRepository.DeleteJournal(selectedJournal);
        if (!opStatus.Status)
            throw new System.Web.Http.HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound));
        return RedirectToAction("Index");
    }
}

这将允许在需要时更好地模拟/伪造/配置映射。您应该确保将IMapper配置为依赖注入到您的控制器中。

如果无法更改为实例api,则需要在运行测试之前确保映射器为初始化

Mapper.Initialize(cfg => {
    cgf.CreateMap<JournalViewModel, Journal>();
});

问题2

你在测试中的安排是

Mock.Arrange(() => journalRepository.DeleteJournal(null)).Returns(new OperationStatus
{
    Status = true
});

正如您所意识到的那样,这不适用于使用实际实例调用journalRepository.DeleteJournal的情况。假设您正在使用Telerik的JustMock,您应该安排一个更灵活的参数。

Mock.Arrange(() => journalRepository.DeleteJournal(Arg.IsAny<Journal>())).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<Journal, JournalViewModel>();
        cfg.CreateMap<JournalViewModel, Journal>();
    });
    var mapper = config.CreateMapper(); // IMapper
    // Simulate PDF file
    var mockFile = Mock.Create<HttpPostedFileBase>();
    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<Journal>())).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<Journal, JournalViewModel>();
        cfg.CreateMap<JournalViewModel, Journal>();
    });
    // Simulate PDF file
    var mockFile = Mock.Create<HttpPostedFileBase>();
    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<Journal>())).Returns(status);
    var controller = new PublisherController(journalRepository, membershipRepository);
    // Act        
    var result = controller.Delete(journalViewModel) as RedirectToRouteResult;
    // Assert
    Assert.AreEqual(result.RouteValues["Action"], "Index");
}