为ASP .NET MVC创建单元测试中的问题 [英] Problems in creating unit test for ASP .NET MVC

查看:76
本文介绍了为ASP .NET MVC创建单元测试中的问题的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在为ASP .NET MVC Controller类创建一些单元测试,但遇到一些非常奇怪的错误:

I am creating some unit tests for my ASP .NET MVC Controller class and I ran into some very strange errors:

我的控制器代码如下:

[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-映射异常:

每次运行测试时,都会收到以下异常:

Every time I run my test I receive the following exception:

测试名称:Delete_Journal测试
全名:Journals.Web.Tests.Controllers.PublisherControllerTest.Delete_Journal
测试源:\ Source \ Journals.Web.Tests \ Controllers \ PublisherControllerTest.cs :第132行
测试结果:测试持续时间失败:0:00:00,3822468

Test Name: Delete_Journal Test
FullName: Journals.Web.Tests.Controllers.PublisherControllerTest.Delete_Journal
Test Source: \Source\Journals.Web.Tests\Controllers\PublisherControllerTest.cs : line 132
Test Outcome: Failed Test Duration: 0:00:00,3822468

结果StackTrace:位于 Journals.Web.Controllers.PublisherController.Delete(JournalViewModel 日记) \ Source \ Journals.Web \ Controllers \ PublisherController.cs:第81行位于 Journals.Web.Tests.Controllers.PublisherControllerTest.Delete_Journal() 在 \ Source \ Journals.Web.Tests \ Controllers \ PublisherControllerTest.cs:line 156结果消息:测试方法 Journals.Web.Tests.Controllers.PublisherControllerTest.Delete_Journal 引发异常:AutoMapper.AutoMapperMappingException:缺少类型 地图配置或不支持的映射.

Result StackTrace: at Journals.Web.Controllers.PublisherController.Delete(JournalViewModel journal) in \Source\Journals.Web\Controllers\PublisherController.cs:line 81 at Journals.Web.Tests.Controllers.PublisherControllerTest.Delete_Journal() in \Source\Journals.Web.Tests\Controllers\PublisherControllerTest.cs:line 156 Result Message: Test method Journals.Web.Tests.Controllers.PublisherControllerTest.Delete_Journal threw exception: AutoMapper.AutoMapperMappingException: Missing type map configuration or unsupported mapping.

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

Mapping types: JournalViewModel -> Journal Journals.Model.JournalViewModel -> Journals.Model.Journal

目标路径:日记

源值:Journals.Model.JournalViewModel

Source value: Journals.Model.JournalViewModel

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

It seems that there is a mapping problem between the classes JournalViewModel and Journal, however I don't know where that is. I added this code to the Application_Start in Global.asax.cs:

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

并且从JournalJournalViewModel的映射正在工作.

And mapping from Journal to JournalViewModel is working.

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

In the end I tried adding Mapper.CreateMap<JournalViewModel, Journal>(); as the first line of the Delete method and then everything works, however I am not sure why.

问题2-HTML异常

一旦使用上述解决方法运行了映射,我就会遇到一个问题,即使我使用Mock覆盖并使其始终为真,来自var opStatus = _journalRepository.DeleteJournal(selectedJournal);的属性Status始终为false.这会引发不应发生的HTML异常.

Once the mapping is running with the workaround above, I have a problem in which the property Status from var opStatus = _journalRepository.DeleteJournal(selectedJournal); is always false, even though I used Mock to override it and make it always true. This causes the throwing of an HTML Exception that shouldn't happen.

编辑

我在Application_Start中更改为:

I changed in my Application_Start to:

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

但是我仍然有同样的错误.

But I still have the same error.

编辑-问题2已解决

事实证明,我忘记了将映射添加到我的单元测试类中,所以我做了以下事情:

It turns out that I forgot to add the mapping to my unit test class, so I did the following:

[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(),所以从不创建映射,所以我必须在单元测试初始化​​中自己做.

And it turns out that this was the source of the problem. I think that since the Global.asax.cs Application_Start() is never called in the unit tests, the Mapping is never created, so I had to do this myself in the unit tests initialization.

推荐答案

问题1

Automapper具有静态和实例API .您应该考虑将实例API与IMapper一起使用,并将其注入到控制器中.

Automapper has both a Static and Instance API. You should consider using the instance API with IMapper and inject that into your controller.

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以便将依赖项注入到控制器中.

That would allow for better mocking/faking/configuration of the mapping as needed. You should make sure to configure IMapper for dependency injection into your controllers.

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

if you are unable to change to the instance api then you need to make sure that the mapper is Initialize before running the tests

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

问题2

您在考试中的安排是

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

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

This as you realized wont work for cases where you call journalRepository.DeleteJournal with an actual instance. Assuming that you are using JustMock from Telerik you should arrange for a more flexible argument.

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");
}

这篇关于为ASP .NET MVC创建单元测试中的问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆