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,
开发者_JAVA百科 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!
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://vibrantcode.com/blog/2010/7/22/using-the-razor-parser-outside-of-aspnet.html
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.
I have written about unit testing Razor views. It is ugly and all sorts of nasty, but it works and I was able to adapt it to test all of my views in all projects.
http://httputility.com/articles/unit-testing-razor-views.html
Although for long lived projects I would suggest waiting for a test harness made by the MVC team.
精彩评论