views:

126

answers:

1

I'm trying out the new Razor view engine with MVC 3 Preview 1 and would really like to write a simple unit test using NUnit/Moq. I haven't seen any examples of this actually being done yet - despite it being one of the real selling features on Razor.

So, if I have a Controller, that uses a DBConext object (EF4 CTP code first) and the view renders a drop-down list based on a list of items provided in a model loaded in an action called on the controller, I'd like to be able to test that the element has items populated into it.

Here's my Controller:

public class WeatherReportController : Controller, IWeatherReportController
{
    private IWeatherDb _weatherDb;

    public WeatherReportController()
    {
        this._weatherDb = new WeatherDb();
    }

    public ActionResult Index()
    {
        WeatherReportIndexModel model = new WeatherReportIndexModel
        {
            Report = new WeatherReport {
                Username = this.HttpContext.User.Identity.Name,
                WeatherType = new WeatherType()
            },
            WeatherTypeList = _weatherDb.GetAllWeatherTypes()
        };
        return View(model);
    }

}

Here's my Model:

public class WeatherReportIndexModel
{
    private IList<WeatherType> _weatherTypeList = new List<WeatherType>();
    public IList<WeatherType> WeatherTypeList { 
        get 
        {
            return _weatherTypeList;
        }
        set 
        {
            _weatherTypeList = value;
        }
    }

    [DisplayName("Type of Weather")]
    public IList<SelectListItem> WeatherTypeSelectItemList
    {
        get
        {
            int id = this.Report.WeatherType == null ? 0 : this.Report.WeatherType.WeatherTypeId;
            List<SelectListItem> selectListItems = this.WeatherTypeList.Select(weatherType => new SelectListItem
                                                                                   {
                                                                                       Value = weatherType.WeatherTypeId.ToString(),
                                                                                       Text = weatherType.Name,
                                                                                       Selected = weatherType.WeatherTypeId == id
                                                                                   }).ToList();
            selectListItems.Insert(0, new SelectListItem { Selected = (this.Report.WeatherType == null), Text = "Select Type of Weather", Value = "0" });
            return selectListItems;
        }
    }

    public WeatherReport Report { get; set; }
}

And here's my View:

@inherits System.Web.Mvc.WebViewPage<Web.UI.Models.WeatherReportIndexModel>

@{
    View.Title = "Index";
    LayoutPage = "~/Views/Shared/_Layout.cshtml";
}

<h2>Index</h2>


@using (Html.BeginForm()) {
    <div>
        <fieldset>
            <legend>New Weather Report</legend>
            <div class="editor-label">
                @Html.LabelFor(m => m.Report.WeatherType.WeatherTypeId)
                @Html.DropDownListFor(m => m.Report.WeatherType.WeatherTypeId, Model.WeatherTypeSelectItemList)
    <input type="submit" value="Log On" />
            </div>
  </fieldset>
 </div>
 }

The test code I have so far is as follows:

[TestFixture]
public class WeatherReportViewTests
{
    [Test]
    public void Can_render_weather_report_index_view_correctly()
    {

        var mockControllerContext = new Mock<ControllerContext>();
        var mockSession = new Mock<HttpSessionStateBase>();

        mockControllerContext.Setup(p => p.HttpContext.Request.HttpMethod).Returns("POST");
        mockControllerContext.Setup(p => p.HttpContext.Request.UserHostAddress).Returns("1.1.1.1");
        mockControllerContext.Setup(p => p.HttpContext.Session).Returns(mockSession.Object);
        mockControllerContext.Setup(p => p.HttpContext.Request.LogonUserIdentity).Returns(WindowsIdentity.GetCurrent());

        var routeData = new RouteData();
        routeData.Values.Add("controller", "WeatherReport");
        routeData.Values.Add("action", "Index");

        var viewEngine = new CshtmlViewEngine();
        var view = viewEngine.FindView(mockControllerContext.Object, "Index", "_Layout", false);
        var viewReponse = view.ToString();

        Assert.That(viewReponse, Contains.Substring("Sunny Intervals"));
    }
}

When running the test I'm just getting a NullReferenceException.

Any ideas/pointers etc. would be welcome. I'd really like to get this working so I can do TDDs on my views in future.

Thanks in advance!

A: 

I would suggest avoiding the CshtmlViewEngine class altogether and firing up the Razor engine yourself. I wrote a blog post about compiling Razor views outside of ASPX here: http://blog.andrewnurse.net/2010/07/22/UsingTheRazorParserOutsideOfASPNet.aspx

In Preview 1 of MVC3, the Razor engine is embedded within System.Web.Mvc and is public (IIRC), so you should be able to find all the classes referenced in that post/sample in System.Web.Mvc.dll.

Once you've compiled the page, just load the generated class, pass in the mocked out context objects, and call Execute(). Since you've got a CodeDOM tree for the page (when you use the Razor engine), you can even tweak the base class so that instead of System.Web.Mvc.WebViewPage, it inherits from a Test page base class that lets you swap in replacement context objects, etc.

anurse
Thanks for that - it doesn't sound anywhere near as straightforward as I was lead to believe :( I'll give it a go though. Soudns like I may need to be patient and wait for a new release of Razor.
dextermixwith
I agree that it's not quite straightforward yet, but I think that Razor dramatically improves the ability for people to write test harnesses that can actually run a Razor view page complete without ever loading up ASP.Net (though you have to mock out elements of the framework, like HTTP Context and other ASP.Net things your view touches).
anurse