ChartResult: Testable ASP.NET MVC 3 controller actions that return Chart
Lately I blogged about charts support in ASP.NET MVC 3 beta. This posting showed how to get chart initialized and sent to client quickly. In this posting I will show you how to use Chart with ChartResult to make our chart operations testable.
Source
You can find source code of this posting from my Visual Studio experiments repository at GitHub.
Source code repository GitHub |
Model
Let’s start with simple model that returns data for chart. In real life you will use database or other data source in model but I save your time and use simple model.
public class ChartModel
{
public virtual IList GetChartData()
{
return new ArrayList
{
new { X = DateTime.Now.AddMonths(-2), Y = 200 },
new { X = DateTime.Now.AddMonths(-1), Y = 300 },
new { X = DateTime.Now, Y = 500 }
};
}
}
Although this model is taken from my charts posting I have minor change here: GetChartData() method is virtual because otherwise Moq cannot mock it.
Controller
Here is the full code of controller. To make controller testable I created constructor that takes model as argument. Don’t forget that in the real-life scenario this model will communicate with some data store.
public class HomeController : Controller
{
private readonly ChartModel _model;
public HomeController(ChartModel model)
{
_model = model;
}
public ActionResult Index()
{
ViewModel.Message = "Welcome to ASP.NET MVC!";
return View();
}
public ChartResult GetChart()
{
var data = _model.GetChartData();
var chart = new Chart(400, 200, ChartTheme.Blue)
.AddTitle("Price enquiries")
.DataBindTable(data, "X");
return new ChartResult(chart, "png");
}
public ActionResult About()
{
return View();
}
}
In my example application I use custom controller factory. If you want to see it, feel free to download the source code.
ChartResult
Here is my ChartResult class that does nothing more than outputs the chart.
public class ChartResult : ActionResult
{
private readonly Chart _chart;
private readonly string _format;
public ChartResult(Chart chart, string format)
{
if (chart == null)
throw new ArgumentNullException("chart");
_chart = chart;
_format = format;
if (string.IsNullOrEmpty(_format))
_format = "png";
}
public Chart Chart
{
get { return _chart; }
}
public string Format
{
get { return _format; }
}
public override void ExecuteResult(ControllerContext context)
{
_chart.Write(_format);
}
}
I made also chart and format available as properties so you have better control over this action result in your tests.
Test
To test if GetChart() works like expected we will use the following primitive test. I am using nUnit and moq for tests. You can add them both to your test project using NuPack.
[TestFixture]
public class HomeControllerTests
{
[Test]
public void GetChartReturnsChart()
{
var data = GetChartData();
var mock = new Mock<ChartModel>();
mock.Setup(c => c.GetChartData())
.Returns(data);
var controller = new HomeController(mock.Object);
var result = controller.GetChart();
mock.Verify();
Assert.IsNotNull(result, "Result is null");
Assert.IsInstanceOf<ChartResult>(result,"Result is not ChartResult");
}
private static IList GetChartData()
{
return new ArrayList
{
new { X = DateTime.Now.AddMonths(-2), Y = 200 },
new { X = DateTime.Now.AddMonths(-1), Y = 300 },
new { X = DateTime.Now, Y = 500 }
};
}
}
Running the test gives us the following nice result.
So, seems like our controller works like expected.
Conclusion
Although new helper for charts is very powerful and works like charm we still need to write some code to keep our controller actions testable. ASP.NET MVC with its flexible architecture helps us out. All we had to do was to remove model instantiation out from controller and add ChartResult class to our project so we follow they way how ASP.NET MVC controllers work.