Search code examples
c#unit-testingmoqmoq-3controllercontext

How to mock a DisplayMode in ControllerContext For Unit Test c#


I want to test an action in my controller that use a controllerContext as a parameter to generate a pdf document based on a 3th part library "Rotativa".
Here is the implementation of the action (function) :

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

}

And here is how I'm trying to get a unit test works :

  • 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

Test Name: DetailsPrint_shouldPrint Test FullName: OPSReviewTest._2A1ControllerTest.DetailsPrint_shouldPrint Test Source: C:\inetpub\wwwroot\OpsReview\OPSReviewTest\Controllers\Api_2A1ControllerTest.cs : line 46 Test Outcome: Failed Test Duration: 0:04:39,3039007 Result StackTrace:
at System.Web.WebPages.DisplayModeProvider.GetDisplayMode(HttpContextBase context) at System.Web.Mvc.ControllerContext.get_DisplayMode() Result Message: Test method OPSReviewTest._2A1ControllerTest.DetailsPrint_shouldPrint threw exception: System.NullReferenceException: Object reference not set to an instance of an object.

Any help or suggestion, Thanks.


Solution

  • You are trying to unit test code you don't own? (Shame, [Bell tolls], shame...)

    If the goal was to test the controller action flow in isolation then it is advised to abstract out the 3rd party PDF generation so that it can be mocked for easier testability.

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

    The abstraction can now be injected into the controller to be used per request action as needed.

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

    And now the unit test can safely mock the 3rd party functionality

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

    Assuming the use of FluentAssertions, the test method should look like this (Pun intended :))

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

    Finally, don't forget to register the interface and its implementation with your DI container in production.