Dynamic ASP.NET MVC - Part 3, Your First Failing Test
- TOC
- Part 1, Setting Up Your Environment
- Part 2, Creating a Better "Compiler"
- Part 3, Your First Failing Test
- Part 4, Dynamic Data Access
- Part 5, Dynamic Data to Views
- Part 6, Mixing in Behavior
- Part 7, Oak Projections in Massive
Listing All Blog Posts
For a blog, we need to well..list all blog posts. Currently we have one Controller action on HomeController.cs that returns Index.cshtml:
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
}
This implementation is incomplete. The view (Index.cshtml) needs to be given a list of blog titles to display. Let's write a test stating that the ViewBag.Blogs needs to be populated with an IEnumerable<dynamic>.
Dealing With Anonymous Types Across Projects
Note: you only have to do this if you didn't use the warmup command in the previous blog post to bootstrap your application. If you've done so, then just skim through this section.
So here's the thing...anonymous types are by default declared as internal classes. There will be points in time where we'll need to perform assertions on anonymous types. We need to ensure that anonymous types declared are visible outside of their respective projects, here's how we do that:
- hold your nose cause this is kinda stinky
- go to your web project, expand the properties folder, and add the following line to AssemblyInfo.cs:
[assembly: InternalsVisibleTo("DynamicBlog.Tests")] //reference to your test project - go to your test project, expand the properties folder, and add the following line to AssemblyInfo.cs:
[assembly: InternalsVisibleTo("DynamicBlog")] //reference to your web project
First Failing Test (written in NSpec)
We'll be using NSpec to write our tests (the installation was covered in Part 2). Visit the NSpec website if you need a crash course on it (you don't need to know all the in's and out's of the framework to start using it). Here are the steps:
- start Growl for Windows if you don't have it already started
- start specwatchr if you don't already have that started (sidekick.bat)
- add a folder under your test project called Controllers
- add a class file called describe_HomeController.cs under that the Controllers folder in your test project
- save all your changes (specwatchr will notify you that the project will be reloaded to accommodate the new file)
- write your first failing test (it's not a lot of code, I've added comments for those that are new to NSpec):
//describe_HomeController inherits from nspec
//access modifier for class isn't required
class describe_HomeController : nspec
{
//declare controller as a private member variable on class
HomeController controller;
//view result property will get set within our tests
ViewResult viewResult;
//view bag will get set within our tests
dynamic viewBag;
//nspec will run this method before each test
void before_each()
{
controller = new HomeController();
}
//define a "method level context" called "when navigating to the home page"
void when_navigating_to_the_home_page()
{
//this "act" is directly related to the context...
//it navigates to the home page and sets the view result and view bag
act = () =>
{
viewResult = controller.Index() as ViewResult;
viewBag = viewResult.ViewBag;
};
//perform your assertion stating that the view bag should have
//a property called Blogs that is a enumerable of dynamic
it["gives a list of blogs"] = () =>
(viewBag.Blogs as IEnumerable<dynamic>).should_not_be_null();
}
}
Saving the describe_HomeController.cs file will fire off specwatchr, which will run the test for you...growl will notify you that you have a failing test (you can look at the console output in the Ruby Command Prompt for additional details).
Make it Pass
Updating the controller like so will make our test pass (it's important to do the simplest thing possible to make the tests pass...doing this will ensure that our test suite is thoroughly specified):
public class HomeController : Controller
{
public ActionResult Index()
{
//this line will make our test pass
ViewBag.Blogs = new List<dynamic>();
return View();
}
}
Saving the HomeController.cs file will fire off specwatchr, which will run impacted tests for you...growl will notify you that all your tests pass (you can look at the console output in the Ruby Command Prompt for additional details).
Flushing Out the Rest of the Implementation
This is not a complete implementation. Looks like we need to expand our test suite. We need to communicate that the blog posts come from some model class. We are going to use a slightly altered version of Massive (which was included in the oak NuGet package). There are a few things we need to do before we can write our next test (appeasing the compiler).
-
Create a class under the Models folder in the MVC Project called Blogs.cs
//yes, that's all you have to type public class Blogs : DynamicRepository { } -
We also need to change our controller to take in the Blogs class:
public class HomeController : Controller { public Blogs Blogs { get; set; } public HomeController(Blogs blogs) { Blogs = blogs; } public ActionResult Index() { ViewBag.Blogs = new List<dynamic>(); return View(); } } - Go to the Package Manager Console Window, select the Test Project as your Default Project and type the following command: install-package Moq
- Save All (specwatchr will notify you that it has been reloaded).
-
In our test, we want to state that blogs should be retrieved from our model class, here is the updated specification:
class describe_HomeController : nspec { HomeController homeController; ViewResult viewResult; dynamic viewBag; //use moq to create a mock of the blog entries //we dont want to worry about the database just yet, so //this allows us to mock out that interaction Mock<Blogs> blogs; //this will represent the blog posts that will retrieved List<dynamic> blogsFromModel; void before_each() { //new up the mock blogs = new Mock<Blogs>(); //new up blogs from model to be an arbitrary collection of //dynamic objects blogsFromModel = new List<dynamic> { new { } }; //setup the mock: blogs.All() returns blogsFromModel blogs.Setup(s => s.All()).Returns(blogsFromModel); homeController = new HomeController(blogs.Object); } void navigating_to_the_home_page() { act = () => { viewResult = homeController.Index() as ViewResult; viewBag = viewResult.ViewBag; }; //change the assertion so that it verifies that the entity //from view bags is the same collection returned from //the model it["gives a list of blogs"] = () => (viewBag.Blogs as IEnumerable<dynamic>).should_be(blogsFromModel); } } -
We make the test pass with the following implementation change:
public class HomeController : Controller { public Blogs Blogs { get; set; } public HomeController(Blogs blogs) { Blogs = blogs; } public ActionResult Index() { ViewBag.Blogs = Blogs.All(); return View(); } } - specwatchr will notify you that all tests pass
Take it All in
We covered our first controller test. As it stands, if you try to start the website, you'll probably get some exceptions (the next parts of this series will explain why and we will fix those things...so don't worry). If Moq didn't make sense, then take some time to read this blog post on Moq and Behavior Driven Development. The important thing to take away from Part 3 is the evolution of the implementation and how we didn't have to press an debug buttons, build buttons, or test buttons (those pieces were handled for us so we could concentrate 100% on the tests and implementation).
A Dialog With Myself
Wait just a second. This "first failing tests" is pretty stupid. Your use of Moq gives us very little value. All you are doing is asserting that the All() method on the Blogs model has been called.
You are absolutely right. The first failing test we created is absolutely useless as it stands, and I wouldn't write this test in the "real world". We are going to elaborate on this test in the next part. I wanted you to get used to the development flow before I open the flood gates.
It also looks like that we are prematurely mocking. Why immediately introduce dependency injection and mocking?
Same reason as above. We as developers have the habit of mocking too much, this is post is a clear example of how silly this is. Also, I wanted Part 3 to be about the development flow and get you used to the rhythm. We are actually going to back out the dependency injection and the mocking in Part 4.
Written: 6/18/2011