16

I am attempting to create a unit test for my controller, but the action I am testing uses a partial view to string function which doesn't want to work in my tests.

private string RenderPartialViewToString(string viewName, object model = null)
{
   if (string.IsNullOrEmpty(viewName))
            viewName = ControllerContext.RouteData.GetRequiredString("action");

   ViewData.Model = model;

   using (System.IO.StringWriter sw = new System.IO.StringWriter())
   {
      ViewEngineResult viewResult = ViewEngines.Engines.FindPartialView(ControllerContext, viewName);
      ViewContext viewContext = new ViewContext(ControllerContext, viewResult.View, ViewData, TempData, sw);
      viewResult.View.Render(viewContext, sw);

      return sw.GetStringBuilder().ToString();
   }
}

This gives me an error of "Object reference not set to an instance of an object" on the line ViewEngineResult viewResult = ViewEngines.Engines.FindPartialView(ControllerContext, viewName);

My setup in the unit test for the controller is (with a few bits removed to simplify it):

var mock = new Mock<ControllerContext>();
mock.SetupGet(p => p.HttpContext.User.Identity.Name).Returns(userName);
if (userName != null)
{
   mock.SetupGet(p => p.HttpContext.Request.IsAuthenticated).Returns(true);
   mock.SetupGet(p => p.HttpContext.User.Identity.IsAuthenticated).Returns(true);
}
else
{
   mock.SetupGet(p => p.HttpContext.Request.IsAuthenticated).Returns(false);
}
var controller = new BlogController();
controller.ControllerContext = mock.Object;

I've not had any luck trying to find a solution or work around. Any help appreciated. Thanks.


As suggested I have tried setting up route data but still getting the error. This is what I have added:

var routeData = new RouteData();
routeData.Values.Add("controller", "BlogController");
mock.SetupGet(m => m.RouteData).Returns(routeData);
AndrewPolland
  • 3,041
  • 4
  • 28
  • 39
  • 1
    ControllerContext.RouteData.GetRequiredString("action") will be failing as you haven't set up RouteData in your mock. So RouteData will be null and calling GetRequiredString on it will give your error. – Andy Nichols Apr 25 '14 at 15:26
  • @AndyNichols Thanks. I've tried a few things and still getting error though. Have edited my post to include. Any pointers on where I'm going wrong? – AndrewPolland Apr 25 '14 at 15:44
  • 1
    Try looking at http://stackoverflow.com/questions/8859077/looking-for-direction-on-unit-testing-a-controller-extension-that-renders-a-part/8861211#8861211 where they have a similar issue and have to stub ViewEngines.Engines – Andy Nichols Apr 25 '14 at 16:03

2 Answers2

23

Final solution thanks to help in the comments.

var mock = new Mock<ControllerContext>();
mock.SetupGet(p => p.HttpContext.User.Identity.Name).Returns(userName);
if (userName != null)
{
   mock.SetupGet(p => p.HttpContext.Request.IsAuthenticated).Returns(true);
   mock.SetupGet(p => p.HttpContext.User.Identity.IsAuthenticated).Returns(true);
}
else
{
   mock.SetupGet(p => p.HttpContext.Request.IsAuthenticated).Returns(false);
}

var routeData = new RouteData();
routeData.Values.Add("controller", "BlogController");
mock.SetupGet(m => m.RouteData).Returns(routeData);

var view = new Mock<IView>();
var engine = new Mock<IViewEngine>();
var viewEngineResult = new ViewEngineResult(view.Object, engine.Object);
engine.Setup(e => e.FindPartialView(It.IsAny<ControllerContext>(), It.IsAny<string>(), It.IsAny<bool>())).Returns(viewEngineResult);
ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(engine.Object);

var controller = new BlogController();
controller.ControllerContext = mock.Object;
AndrewPolland
  • 3,041
  • 4
  • 28
  • 39
  • 2
    Any ideas if you wanted to actually render a view here? – Don Rolling Oct 06 '14 at 16:11
  • @DonRolling Can't remember the exact reason for needing this now. But I think I was unit testing an action which would return the string of a partial view. I wasn't testing if the string had any value, more that some of the other functions were called with the action. Just needed RenderPartialViewToString() to work so that the unit test could run. – AndrewPolland Oct 08 '14 at 12:01
  • 1
    @DonRolling I am looking for a way to render a view as well. Did you figure out something? – Shahin Apr 04 '16 at 16:10
  • I was having exactly the same issue and thanks for the solution! – Chih-Ho Andy Chou Sep 11 '17 at 15:47
  • Thanks a lot, @AndrewPolland. It seems that after a day of frustration I found a working code. (I'm very new to both .NET MVC and unit testing.) In my case it was RenderViewToString(ControllerContext context, ...) method from https://www.codemag.com/article/1312081 which required ControllerContext parameter and returned a partial view string. RenderViewToString() uses context.Controller property inside. So I modified your code by adding mock.SetupGet(c => c.Controller).Returns(controller); – vkelman Oct 15 '19 at 20:01
1

Here's a version using AutoMoq that renders a string of your choice.

Subject.ControllerContext = new ControllerContext(
    Mocked<HttpContextBase>().Object,
    new RouteData(),
    Subject);

ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(Mocked<IViewEngine>().Object);
Mocked<IViewEngine>()
    .Setup(x => x.FindPartialView(Subject.ControllerContext,
                                  It.IsAny<string>(), It.IsAny<bool>()))
    .Returns(new ViewEngineResult(Mocked<IView>().Object,
                                  Mocked<IViewEngine>().Object));
Mocked<IView>()
    .Setup(x => x.Render(It.IsAny<ViewContext>(), It.IsAny<TextWriter>()))
    .Callback((ViewContext c, TextWriter w) => w.WriteLine("RENDERED"));
Neal Ehardt
  • 10,334
  • 9
  • 41
  • 51