2

我正在为我的 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 测试全名
:Journals.Web.Tests.Controllers.PublisherControllerTest.Delete_Journal
测试源:\Source\Journals.Web.Tests\Controllers\PublisherControllerTest.cs:第 132 行
测试结果:失败测试持续时间: 0:00:00 ,3822468

结果 StackTrace:在 \Source\Journals.Web\Controllers\PublisherController.cs 中的 Journals.Web.Controllers.PublisherController.Delete(JournalViewModel journal) 处:\Source 中 Journals.Web.Tests.Controllers.PublisherControllerTest.Delete_Journal() 的第 81 行\Journals.Web.Tests\Controllers\PublisherControllerTest.cs:line 156 结果消息:测试方法 Journals.Web.Tests.Controllers.PublisherControllerTest.Delete_Journal 抛出异常:AutoMapper.AutoMapperMappingException:缺少类型映射配置或不支持的映射。

映射类型:JournalViewModel -> Journal Journals.Model.JournalViewModel -> Journals.Model.Journal

目标路径:日记

源值:Journals.Model.JournalViewModel

JournalViewModel类和之间似乎存在映射问题Journal,但是我不知道那在哪里。我将此代码添加到Application_Startin Global.asax.cs

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

Journal并且从to映射JournalViewModel正在工作。

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

问题 2 - HTML 异常

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

编辑

我将 Application_Start 更改为:

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

但我仍然有同样的错误。

编辑 - 问题 2 已解决

原来我忘记将映射添加到我的单元测试类中,所以我做了以下操作:

[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(),因此从未创建映射,因此我必须在单元测试初始化​​中自己执行此操作。

4

1 回答 1

5

问题 1

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

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 Arrangements 中的处理参数

完整测试:实例 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");
}
于 2016-10-05T07:48:18.067 回答