RazorGenerator.Test - Manipulating views post-initialisation

Oct 10, 2011 at 5:28 PM

First of all: Great work! This project is exactly what I was looking for.

I was wondering if it would be possible to alter the RazorGenerator.Testing API a little... At the moment, any changes to ViewData and other read/write properties on the View get overriden by the WebPageViewExtensions.Render method, limiting options for setting up the view.

For example, there's currently no way to set a RouteCollection, meaning that link URLs never get rendered properly. There's no way to mock this either, because it's static methods all the way down (LinkExtensions.ActionLink -> HtmlHelper.GenerateLink -> UrlHelper.GenerateUrl -> RouteCollection.GetVirtualPathForArea). However, the innermost method makes use of the routeCollection passed down from the HtmlHelper instance. If you had an opportunity to set up routes then you could check things like whether your view rendered a Create or Edit link.

The easiest thing would be to make WebPageViewExtensions.Initialize public and remove the first line of the Render method (which just calls Initialize). It would be consistent to move the line that sets the Model out of the Render method as well. So the usage would look something like this:

 

var mockHttpContext = new Mock<HttpContextBase>
var mockModel = new Mock<IMyModel>

var myView = new MyView()
myView.Initialize(mockHttpContext.Object)

MyApplication.RegisterRoutes(myView.Html.RouteCollection)
myView.ViewData.Model = mockModel.Object;

HtmlDocument output = myView.RenderAsHtml()

// Assertions below...

 

 

Coordinator
Oct 10, 2011 at 5:43 PM

Note that Render and RenderAsHtml have overloads where you can pass in both an HttpContextBase and a model. So your code above might become:

var mockHttpContext = new Mock<HttpContextBase>
var mockModel = new Mock<IMyModel>

var myView = new MyView()

MyApplication.RegisterRoutes(myView.Html.RouteCollection)

HtmlDocument output = myView.RenderAsHtml(mockHttpContext, mockModel.Object)

// Assertions below...

Does that get you a step closer?

Oct 10, 2011 at 5:55 PM
Edited Oct 10, 2011 at 5:58 PM

Thanks for the quick response...

Being able to set the HttpContext doesn't help with the specific thing that I want to change, since it's a property of the HtmlHelper. However, I just worked out that this property is itself initialised from another static property (RouteTable.Routes), so I can in fact already achieve what I want with the following (without any changes to the RazorGenerator.Testing codebase):

 

var mockHttpContext = new Mock<HttpContextBase>()
var mockModel = new Mock<IMyModel>()
var myView = new MyView() MyApplication.RegisterRoutes(RouteTable.Routes) HtmlDocument output = myView.RenderAsHtml(mockHttpContext, mockModel.Object)

I still think it might be useful to be able to call Render and Initialize separately, but I don't have an immediate requirement for it anymore. I guess being able to specify the Model, HttpContext, and all of the MVC static properties probably covers most eventualities.

Coordinator
Oct 10, 2011 at 5:57 PM

Great. If we get to a point where it becomes necessary, we can look into making that change.

Jan 5, 2012 at 10:25 PM

First of all, thanks for RazorGenerator - it's enabled me to achieve something close to the Holy Grail of unit-testing VoiceXML views (for which MVC 3, by the way, is ideal!). 

I'm trying to get my compiled view to render a Url.Action call correctly in my unit tests, and came across this post. I've added the line

MyApplication.MvcApplication.RegisterRoutes(RouteTable.Routes);

and also tried it with the application's MvcApplication_Accessor's method as well.

I keep getting null references in the view's URL.Action call (I'm using the 4-argument version).

Any ideas where I could be going wrong?

Thanks

Jim Stanley

Blackboard Connect.

Coordinator
Jan 7, 2012 at 6:19 AM

Could you include the full stack where the null ref exception happens? Also, are you able to repro this issue from a new MVC3 project (with RazorGenerator enabled)? Just to see whether it's a general issue or something specific to your set up.

thanks,
David 

Jan 9, 2012 at 10:08 PM

David,

Here's the stack trace - I'll try a new project when I get a chance.

Thanks,

Jim Stanley

Blackboard Connect

System.NullReferenceException was unhandled by user code
  Message=Object reference not set to an instance of an object.
  Source=System.Web
  StackTrace:
       at System.Web.Routing.RouteCollection.GetUrlWithApplicationPath(RequestContext requestContext, String url)
       at System.Web.Routing.RouteCollection.GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
       at System.Web.Mvc.RouteCollectionExtensions.GetVirtualPathForArea(RouteCollection routes, RequestContext requestContext, String name, RouteValueDictionary values, Boolean& usingAreas)
       at System.Web.Mvc.UrlHelper.GenerateUrl(String routeName, String actionName, String controllerName, RouteValueDictionary routeValues, RouteCollection routeCollection, RequestContext requestContext, Boolean includeImplicitMvcValues)
       at System.Web.Mvc.UrlHelper.GenerateUrl(String routeName, String actionName, String controllerName, String protocol, String hostName, String fragment, RouteValueDictionary routeValues, RouteCollection routeCollection, RequestContext requestContext, Boolean includeImplicitMvcValues)
       at System.Web.Mvc.UrlHelper.Action(String actionName, String controllerName, Object routeValues, String protocol)
       at ShimmerIVR.Views.Survey.PartialSurvey.Execute() in c:\TFS\BCI\Branches\Release5.1\Web\Site\BCI.Web.Site.ShimmerIVR\ShimmerIVR\Views\Survey\PartialSurvey.cshtml:line 11
       at RazorGenerator.Testing.WebViewPageExtensions.Render[TModel](WebViewPage`1 view, HttpContextBase httpContext, TModel model)
       at IVRUnitTests.SurveyViewTests.IsPartialSurveyViewValid() in C:\TFS\BCI\Branches\Release5.1\Web\Site\BCI.Web.Site.ShimmerIVR\IVRUnitTests\SurveyViewTests.cs:line 83
  InnerException:

Coordinator
Jan 10, 2012 at 1:15 AM

Seems it's deep in MVC. If you find a simple repro that we can try here, please file a bug and I'll investigate.

Jan 11, 2012 at 1:33 PM

Are you allowing RazorGenerator to set up the mock HttpContext for you, or are you providing your own mock HttpContext using the Render/RenderAsHtml overload that takes an HttpContextBase parameter? - If the latter, maybe your mock context isn't set up properly (in particular, its Request property has to return something sensible).

Jan 11, 2012 at 4:55 PM

Hmm, it looked like from the above discussion that RazorGenerator couldn't set up the mock context (I'm using Moq) - if it can, I'll be happy to include it.  Here's the (abbreviated) code - if something looks amiss, I'm happy to try whatever you recommend.  Thanks!

[TestClass]
public class ReplayTests
{
    private TestContext testContextInstance;
    private Mock<HttpContextBase> mockContext;
    private Mock<HttpSessionStateBase> mockSession;
    private Mock<HttpRequestBase> mockRequest;
    ...
}

[TestInitialize]
public void Setup()
{
    mockContext = new Mock<HttpContextBase>();
    mockSession = new Mock<HttpSessionStateBase>();
    mockRequest = new Mock<HttpRequestBase>();
    mockContext.Setup(m => m.Request).Returns(mockRequest.Object);
    mockContext.Setup(m => m.Session).Returns(mockSession.Object);
    ShimmerIVR.MvcApplication_Accessor.RegisterRoutes(RouteTable.Routes);
    ...
}

[TestMethod]
public void WelcomeTestWithPhoneNumber()
{
    var view = new ReplayWelcome();
    string result = view.Render(mockContext.Object); // view throws Url.Action exception here
    ...
}

Jan 11, 2012 at 10:44 PM

The discussion above wasn't about being able to mock the HttpContext (which is possible), but about being able to set certain properties of the view (e.g. its ViewData) before rendering.

 

According to Reflector, the method that's throwing the null ref exception accesses both HttpContext.Request and HttpContext.Response. Try mocking the response as well, e.g.

mockContext.Setup(m =>m.Response).Returns(new Mock<HttpResponseBase>())

Or, if you don't need any particular behaviour on the mock context (which looks to be the case), simply let RazorGenerator.Testing set up the HttpContext for you by changing this line:

 string result = view.Render(mockContext.Object);

to just:

 string result = view.Render();

Jan 12, 2012 at 12:38 AM

Darn - I do indeed need the mocked context because I also mock the Session object to store some "global" data used throughout the controller's workflow.

Mocking the Response gave the same error.

I'll work on replicating the issue with a smaller project and send it on - let me know where I can attach a file.

Thanks again for all your help.  Even with the minor glitch I'm experiencing, RazorGenerator is *extremely* valuable.  I'll be doing a presentation in about 2 weeks where I will definitely be singing your praises.

Jim Stanley

Blackboard Connect

Jan 12, 2012 at 10:22 PM
Edited Jan 12, 2012 at 10:22 PM

Does using view.Render() at least solve the null ref issue? - If so, then RazorGenerator.Testing's own mock HttpContext must be working, so you could crib from it (see CreateMockContext in http://razorgenerator.codeplex.com/SourceControl/changeset/view/d31a2e0e1bfe#RazorGenerator.Testing%2fWebViewPageExtensions.cs)... Either remove things you don't need until it breaks, or copy bits from it until your mock HttpContext works.

If you get the same null ref exception even when using view.Render() and not passing in a mock HttpContext, then I suppose working up a small repro case is your own option.