如何在C#的ControllerContext中模拟DisplayMode以进行单元测试

3

我想测试控制器中的一个操作,该操作使用控制器上下文作为参数,基于第三方库“Rotativa”生成pdf文档。
这是该操作(函数)的实现方式:

public ActionResult DetailsPrint(int? id)
{
    var a = new ViewAsPdf();
    a.ViewName = "../Ops/_2A1/Details";
    a.Model =UnitOfWork._2A1s.Get(id.Value);
    var pdfBytes = a.BuildPdf(ControllerContext);

    // return ActionResult
    MemoryStream ms = new MemoryStream(pdfBytes);
    return new FileStreamResult(ms, "application/pdf");

}

以下是我尝试使单元测试工作的方法:

  • Constructor

    public _2A1ControllerTest()
    {
        _mockRepository = new Mock<I2A1Repository>();
        var mockUoW = new Mock<IUnitOfWork>();
        _mockHttpContext = new Mock<HttpContextBase>();
        _mockRequest = new Mock<HttpRequestBase>();
        _mockDisplayModeContext = new Mock<IDisplayMode>();
        mockUoW.SetupGet(u => u._2A1s).Returns(_mockRepository.Object);
        _mockHttpContext.SetupGet(x => x.Request).Returns(_mockRequest.Object);
        _controller = new _2A1Controller(mockUoW.Object);
        _controller.MockCurrentUser("test.admin");
        _controller.ControllerContext = new ControllerContext(_mockHttpContext.Object, new System.Web.Routing.RouteData(), _controller);
    }
    
  • Test Function

    [TestMethod]
    public void DetailsPrint_shouldPrint()
    {
        var result = _controller.DetailsPrint(1) as ActionResult;
        result.Should().BeOfType<ActionResult>();
    }
    

    When I execute the test I get the follwing error bellow : enter image description here

测试名称:DetailsPrint_shouldPrint 测试全名:OPSReviewTest._2A1ControllerTest.DetailsPrint_shouldPrint 测试来源:C:\inetpub\wwwroot\OpsReview\OPSReviewTest\Controllers\Api_2A1ControllerTest.cs : line 46 测试结果:失败 测试持续时间:0:04:39,3039007 结果堆栈跟踪:
at System.Web.WebPages.DisplayModeProvider.GetDisplayMode(HttpContextBase context) at System.Web.Mvc.ControllerContext.get_DisplayMode() 结果信息: 测试方法 OPSReviewTest._2A1ControllerTest.DetailsPrint_shouldPrint 抛出异常: System.NullReferenceException: 对象引用未设置为对象的实例。

有任何帮助或建议,谢谢。


你在该方法中想要测试什么内容? - Nkosi
2个回答

1
你正在尝试对你不拥有的代码进行单元测试?(羞耻,[钟声响起],羞耻...)
如果目标是在隔离中测试控制器动作流,则建议将第三方PDF生成抽象出来,以便可以模拟更轻松地进行测试。
public interface IViewAsPdfWrapper {
    string ViewName { get; set; }
    object Model { get; set; }
    byte[] BuildPdf(ControllerContext context);
}

public class ViewAsPdfWrapper : IViewAsPdfWrapper {
    private readonly ViewAsPdf view;
    public ViewAsPdfWrapper() {
        view = new ViewAsPdf();
    }
    public string ViewName { get; set; }
    public object Model { get; set; }
    public byte[] BuildPdf(ControllerContext context) {
        view.ViewName = ViewName;
        view.Model = Model;
        return view.BuildPdf(context);
    }
}

抽象化现在可以注入到控制器中,根据需要在每个请求操作中使用。
public class _2A1Controller : Controller {
    private readonly IUnitOfWork UnitOfWork;
    private readonly IViewAsPdfWrapper viewAsPdf;

    public _2A1Controller(IUnitOfWork uow, IViewAsPdfWrapper viewAsPdf) {
        this.UnitOfWork = uow;
        this.viewAsPdf = viewAsPdf;
    }

    public ActionResult DetailsPrint(int? id) {
        var a = viewAsPdf;
        a.ViewName = "../Ops/_2A1/Details";
        a.Model = UnitOfWork._2A1s.Get(id.Value);
        var pdfBytes = a.BuildPdf(ControllerContext);

        // return ActionResult
        MemoryStream ms = new MemoryStream(pdfBytes);
        return new FileStreamResult(ms, "application/pdf");
    }

}

现在,单元测试可以安全地模拟第三方功能。
public _2A1ControllerTest() {    
    _mockRepository = new Mock<I2A1Repository>();
    var mockUoW = new Mock<IUnitOfWork>();
    mockUoW.SetupGet(u => u._2A1s).Returns(_mockRepository.Object);

    var mockViewAsPdf = new Mock<IViewAsPdfWrapper>();
    mockViewAsPdf.Setup(m => m.BuildPdf(It.IsAny<ControllerContext>()))
        .Returns(() => new byte[0]);

    _mockRequest = new Mock<HttpRequestBase>();
    _mockHttpContext = new Mock<HttpContextBase>();
    _mockHttpContext.SetupGet(x => x.Request).Returns(_mockRequest.Object);

    _controller = new _2A1Controller(mockUoW.Object, mockViewAsPdf.Object);
    _controller.MockCurrentUser("test.admin");
    _controller.ControllerContext = new ControllerContext(_mockHttpContext.Object, new System.Web.Routing.RouteData(), _controller);

}

假设使用FluentAssertions,测试方法应该像这样(意在双关):
[TestMethod]
public void DetailsPrint_shouldPrint() {
    var result = _controller.DetailsPrint(1) as ActionResult;
    result.Should()
        .NotBeNull()
        .And
        .BeAssignableTo<ActionResult>();
}

最后,在生产环境中不要忘记将接口及其实现注册到您的 DI 容器中。

首先,非常感谢您的帮助,您为我节省了很多时间和搜索,我真的非常感激。 对于您的问题,这是我的自己的代码,并且是我第一次使用模拟和模拟第三方库的经验。 您在这方面是学者。 - Le-Mr-Ruyk
1
当我说自己的代码时,我指的是第三方库。这只是一句俗语,旨在开玩笑 :). 第三方库提供者在发布代码之前应该测试他们的代码。因此,试图测试它是浪费你的时间。通过将其包装在你自己的抽象层中,你就可以控制模拟和测试,具有更大的灵活性。 - Nkosi
啊,好的,感谢您提供的所有建议和花费的时间。 - Le-Mr-Ruyk

0

你忘记给DsiplayMode属性分配ControllerContext了,加上这个:

_controller.ControllerContext.DisplayMode=_mockDisplayModeContext.Object;

感谢@esiprogrammer的回答,但它没有起作用。 - Le-Mr-Ruyk
@ayyoub,你的意思是它没有起作用吗?你还是在同一行有相同的异常吗? - esiprogrammer
哎呀,伙计,我还是遇到了同样的异常。昨天我花了4个小时去解决它,但没有结果。然后我把它放在stackoverflow上,希望有人之前也遇到过同样的问题。感谢你的互动。 - Le-Mr-Ruyk

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接