【问题标题】:Mocking a controller to test ViewEngine inside an Area - nullreference and RouteData模拟控制器以在区域内测试 ViewEngine - nullreference 和 RouteData
【发布时间】:2018-01-23 07:34:28
【问题描述】:

我的 MVC 站点中有一个 Area。该区域具有典型的 Controller/Model/View 设置。

作为控制器,我有以下代码:

public class DocumentCreatorController : Controller
{
    // GET: Templates/DocumentCreator
    public ActionResult OfferTemplate(BaseDocumentViewModel data)
    {
        return this.Pdf(nameof(OfferTemplate), data, "File.pdf");
    }
}

this.Pdf 方法做了一些事情,但有趣的是它归结为 ViewEngine 调用:

var viewResult = ViewEngines.Engines.FindPartialView(controllerContext, partialViewName);

在这里,我将FindPartialView 称为ControllerContextPartialViewName。我的PartialViewName 来自控制器操作OfferTemplatenameof(OfferTemplate)。我认为controllercontext 是我的挑战。

我的挑战:

当我想在单元测试中进行设置时(使用 Moq),我有以下基于页面的代码,例如 Mocking The RouteData Class in System.Web.Routing for MVC applicationsMocking Asp.net-mvc Controller Context

[TestMethod]
public void OfferTemplate()
{
    var ctr = SetupControllerWithContext();

}

private static DocumentCreatorController SetupControllerWithContext()
{
    var routeData = new RouteData();
    routeData.Values.Add("controller", "DocumentCreatorController");
    routeData.Values.Add("action", "OfferTemplate");


    var request = new Mock<HttpRequestBase>();
    request.Expect(r => r.HttpMethod).Returns("GET");
    var mockHttpContext = new Mock<HttpContextBase>();
    mockHttpContext.Expect(c => c.Request).Returns(request.Object);
    var controllerContext = new ControllerContext(mockHttpContext.Object
        , routeData, new Mock<ControllerBase>().Object);

    DocumentCreatorController ctr = new DocumentCreatorController();
    ctr.ControllerContext = controllerContext;
    return ctr;
}

这给出了以下错误:

Eesy.Websites.Api.Tests.Controllers.DocumentCreatorControllerTest.OfferTemplate 抛出异常: System.NullReferenceException:对象引用未设置为对象的实例。

这个我不明白。

我的文件夹设置:

在调用 FindPartialView 时在 ControllerContext 上调试图像:

有人有想法吗? 是不是因为RouteData设置错了?

【问题讨论】:

  • 它需要是routeData.Values.Add("controller", "DocumentCreator");(不是`"DocumentCreatorController")
  • 您正在尝试模拟和测试框架代码。将该功能抽象到您控制的代码中,以便您可以在需要时单独进行测试。这似乎是XY problem。您要达到的最终目标是什么?
  • @Nkosi 好点。我正在尝试使用单元测试自动生成 PDF(HTML 到 PDF),因此我可以验证 PDF 生成过程是否有效。我希望使用我的控制器来做到这一点,但显然,创建一个服务来避免这一切是有意义的:-)
  • @StephenMuecke 你当然是对的。我更新了它 - 但也是一个空引用错误

标签: c# asp.net-mvc unit-testing asp.net-mvc-4 moq


【解决方案1】:

您正在尝试模拟和测试框架代码。将该功能抽象到您控制的代码中,以便您可以在需要时单独进行测试。

目前,动作和扩展控制器与外部 3rd 方依赖项紧密耦合。如果目标是单独测试控制器操作流,则建议抽象出第 3 方 PDF 生成,以便对其进行模拟以便于测试。

public interface IDocumentService {
    ActionResult ToPdf(Controller arg1, string arg2, object arg3, string arg4);
}

控制器将通过构造函数注入显式依赖此抽象。

public class DocumentCreatorController : Controller {
    private readonly IDocumentService render;

    DocumentCreatorController(IDocumentService render) {
        this.render = render;
    }

    // GET: Templates/DocumentCreator
    public ActionResult OfferTemplate(BaseDocumentViewModel data) {
        return render.ToPdf(this, nameof(OfferTemplate), data, "File.pdf");
    }
}

所以现在要测试控制器的 pdf 生成过程,您只需要模拟您的抽象。

[TestMethod]
public void OfferTemplate() {
    //Arrange    
    var serviceMock = new Mock<IDocumentService>();
    //...setup mock for use case

    var controller = new DocumentCreatorController(serviceMock.Object);
    var data = new BaseDocumentViewModel {
        //...
    };

    //Act
    var actual = controller.OfferTemplate(data);

    //Assert
    //...assert behavior
}

服务的实际实现将封装实际功能,并将与抽象一起注册到依赖注入容器中。

要测试实际生成,您需要进行集成测试,这是另一个主题。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-06-29
    • 2014-01-16
    • 1970-01-01
    • 2018-08-15
    • 1970-01-01
    • 2012-08-01
    • 2013-08-04
    相关资源
    最近更新 更多