Purpose
The purpose of this article is to explain what TDD is and give an example in terms of the process.
So what does TDD stand for, well according to Wikipedia it is Test Driven Development but having read around the subject looking at blogs from Brad Wilson it should really be Test Driven Design, or as his want Design By Example but this doesn't fit in the acronym. I'll stick with Test Driven Design.
Approach
Instead of writing the controller and repository first instead I will create the test and use Visual Studio 2010 to automatically generate the classes, methods and properties as dictated by the test.
Patterns
So what about patterns? Well in this example I will use the Dependency Injection, Repository, Mock Object Design and MVC patterns.
Project
A simple repository call that returns a list of blog entries with URLs built from the title field.
Toolset
To use Moq for the Mock Object Framework
Version: 4.0.10827
Mock objects are faked objects that imitate the activities of real objects in a controlled manner.
To mock an interface to the data access component while testing the business logic. This avoids writing code until you are ready to do so.
Moq takes advantage of recent VB.NET and C# language features such as lambdas and generics. When creating mock objects with Moq, you use lambda expressions to represent the methods and properties that you want to mock.
Download instructions
Download Moq from: http://code.google.com/p/moq/downloads/listEnsure you unblock the zip file once you have downloaded it.
For this example I am using .NET 4 so I will need to use the Moq DLL from:
\Moq.4.0.10827.Final\Moq.4.0.10827\NET40
NUnit
Version: 2.5.10.11092
NUnit is a unit testing framework for .NET. The units of code are tested to ascertain if they are fit for use.
Download instructions
Download NUnit from here: http://www.nunit.org/index.php?p=downloadUse the .msi file.
Install with Typical and use the defaults.
Follow the installation guide here:
http://www.nunit.org/index.php?p=installation&r=2.5.10
C:\Program Files (x86)\NUnit 2.5.10\bin\net-2.0\nunit.exe.config
To add NUnit to Visual Studio tools menu in VS 2010
In Visual Studio have the test project highlighted before running NUnit from the Tool menu.
MVC for the Web Application Framework
Version: ASP.NET MVC 3.
The Model View Controller is a software architecture software pattern.
Coding
Ok, let's get our hands dirty.
Firstly I know that I want to use MVC for this project so we kick off with an empty MVC framework.
Create the MVC project
In Visual Studio 2010: New Project...
ASP.NET MVC 3 Web Application
TDDBlog
Empty
View engine: Razor
Create a new Class Library Project called TDDBlog.Tests
Right Click and Add a reference to TDDBlog from the Projects tab
Right click and Add a reference to nunit.framework from the .NET
Create a new folder structure \Library
Copy the Moq.dll to \Library\Moq
Right click and Add a reference to Moq.dll
Delete Class1.cs
Create a test for the controller
So we know that we are dealing with a Blog so I will want a BlogController. But before I go ahead and start building that up I will create the test first.
So in the TDDBlog.Tests
Create a new folder structure \Controllers
Add a new class: BlogControllerTest and make it public, as we are using NUnit we want to tell NUnit that the class we are using is testable, this requires us to reference the NUnit framework in our code and add the TestFixture attribute to the class:
using NUnit.Framework;
namespace TDDBlog.Tests.Controllers
{
[TestFixture]
public class BlogControllerTest
{
}
}
Let's check to see if it builds.
Ok, hopefully you had some joy there. So I want to return a list of blog entries and check the format of the URL.
As we are using MVC the test is a call on the Index Action:
Index returns a list of blog entries with correct Urls
[Test]
public void IndexReturnsAListOfBlogEntriesWithCorrectUrls()
{
}
Now we have our empty test lets run it in NUnit.
Now we have NUnit set up in the Tools menu we can run it from there. Ensure you highlight the TDDBlog.Tests project.
If it does not load with the correct DLL, simply: File > Open Project and select the TDDBlog.Tests.dll.
Click Run to execute the test.
We will see a green light, this is the default behaviour; if no test content is present then the is nothing to fail.
Start building the test and let the compiler guide you
So our blog will have BlogEntry class objects, to set this up we will Mock a list of these.
A Mock defines the results from calls it does not set the content on objects. I like Moq as it uses Lambda expressions.
Create a Mock for BlogEntry:
var mockBlogEntry1 = new Mock<BlogEntry> ();
using Moq;
The type or namespace name 'BlogEntry' could not be found (are you missing a using directive or an assembly reference?)
So no surprise, we need to create this class.
Left click on BlogEntry in the test method. If you have reSharper installed you will get a red light bulb appear on the left allowing you to create a new class called BlogEntry Visual Studio 2010 has the same functionality. Hover over BlogEntry and a icon with a tool tip of, 'Options to help bind the selected item' appears; extend this and click on 'Generate new type...'.
This will allow you to specify the location and access:
- Access: public
- Kind: class
- Location: TDDBlog
- Create new file: Models\BlogEntry.cs
Next we want to set some fields on the new class again this do this from the test.
const int id1 = 1;
const string title1 = "My first blog Entry";
const string content1 = "I love blogging, it is so cool";
mockBlogEntry1.SetupGet(x => x.Id).Returns(id1);
mockBlogEntry1.SetupGet(x => x.Title).Returns(title1);
mockBlogEntry1.SetupGet(x => x.Content).Returns(content1);
So your BlogEntry class should look like:
namespace TDDBlog.Models
{
public class BlogEntry
{
public Id Id { get; set; }
public Title Title { get; set; }
public Content Content { get; set; }
}
}
namespace TDDBlog.Models
{
public class BlogEntry
{
public virtual int Id { get; set; }
public virtual string Title { get; set; }
public virtual string Content { get; set; }
}
}
const int id2 = 2;
const string title2 = "I'm still in to this";
const string content2 = "I'm still enjoying my blogging";
var mockBlogEntry2 = new Mock<BlogEntry>();
mockBlogEntry2.SetupGet(x => x.Id).Returns(id2);
mockBlogEntry2.SetupGet(x => x.Title).Returns(title2);
mockBlogEntry2.SetupGet(x => x.Content).Returns(content2);
const int id3 = 3;
const string title3 = "OK!";
const string content3 = "Ok, I'm done!";
var mockBlogEntry3 = new Mock<BlogEntry>();
mockBlogEntry3.SetupGet(x => x.Id).Returns(id3);
mockBlogEntry3.SetupGet(x => x.Title).Returns(title3);
mockBlogEntry3.SetupGet(x => x.Content).Returns(content3);
The next thing to do is to work out how we are going to store this, notice I say how not where. Let's say we are going to use a repository pattern. Mock an IBlogsRepository (again don't create it first).
var blogRepository = new Mock<IBlogsRepository>();
Follow the same process as before:
- Access: public
- Kind: interface
- Project: TDDBlog
- Create new file: Models\IBlogsRepository
blogRepository
.Setup(p => p.GetAllBlogEntries())
.Returns(new List<BlogEntry> { new BlogEntry.Object });
Setup works in the same way as SetupGet but is for method calls not Properties on a Class.
Add the interface method for GetAllBlogEntries by clicking on the method and generating from the test: 'Generate method stub for 'GetAllBlogEntries' for 'TDDBlog.Models.IBlogsRepository'
The interface will look like:
namespace TDDBlog.Models
{
public interface IBlogsRepository
{
object GetAllBlogEntries();
}
}
namespace TDDBlog.Models
{
public interface IBlogsRepository
{
IEnumerable<BlogEntry> GetAllBlogEntries();
}
}
Test the Controller
We will inject the controller with the repository.
In the test we will initialise the controller - we will start with the test code before getting the test code to create the Controller:
BlogController blogController = new BlogController((IBlogsRepository)blogRepository.Object);
- Access: public
- Kind: class
- Project: TDDBlog
- Create new file: Controllers\BlogController.cs
Again click and hover on the BlogController, and 'Generate constructor stub in 'TDDBlog.Controllers.BlogController'.
This generates:
namespace TDDBlog.Controllers
{
public class BlogController
{
private Models.IBlogsRepository iBlogsRepository;
public BlogController(Models.IBlogsRepository iBlogsRepository)
{
// TODO: Complete member initialization
this.iBlogsRepository = iBlogsRepository;
}
}
}
Inherit the BlogController from Controller and add a reference to System.Web.Mvc.
Next we need to call the controller method to get the result. We are going to assume that the Index call is going to be used:
ViewResult viewResult = blogController.Index() as ViewResult;
Then get Visual Studio 2010 add the reference to the file by hover etc...
Create the Index method in the Controller by the usual method as described above.
public ViewResult Index()
{
throw new NotImplementedException();
}
Start testing
We want to get the URL for each blog entry but this will not be in the repository.
List<BlogEntry> blogEntries = (System.Collections.Generic.List)viewResult.ViewData.Model;
BlogEntry blogEntry1 = blogEntries[0];
Assert.AreEqual(id1, blogEntry1.Id);
Assert.AreEqual(title1, blogEntry1.Title);
const string url1 = "my-first-blog-entry";
Assert.AreEqual(url1, blogEntry1.Url);
Assert.AreEqual(content1, blogEntry1.Content);
BlogEntry blogEntry2 = blogEntries[1];
Assert.AreEqual(id2, blogEntry2.Id);
Assert.AreEqual(title2, blogEntry2.Title);
const string url2 = "I'm still in to this";
Assert.AreEqual(url2, blogEntry2.Url);
Assert.AreEqual(content2, blogEntry2.Content);
BlogEntry blogEntry3 = blogEntries[2];
Assert.AreEqual(id3, blogEntry3.Id);
Assert.AreEqual(title3, blogEntry3.Title);
const string url3 = "ok";
Assert.AreEqual(url3, blogEntry3.Url);
Assert.AreEqual(content3, blogEntry3.Content);
public string Url { get; set; }
Right, that is the test code complete, so what happens when we run our test?
Expected failure
So we are seeing red! Let's look at the output window:
TDDBlog.Tests.Controllers.BlogControllerTest.IndexReturnsAListOfBlogEntriesWithCorrectUrls:
TDDBlog.Tests.Controllers.BlogControllerTest.IndexReturnsAListOfBlogEntriesWithCorrectUrls:
System.NotImplementedException : The method or operation is not implemented.Not a big shock as we have not implemented the Index method on the controller yet.
Let's do this now:
public ViewResult Index()
{
return View(iBlogsRepository.GetAllBlogEntries());
}
Ok, run again. Hmm red again what now:
TDDBlog.Tests.Controllers.BlogControllerTest.IndexReturnsAListOfBlogEntriesWithCorrectUrls:
Expected: "my-first-blog-entry"
But was: null
So we need to change the controller:
public ViewResult Index()
{
IEnumerable<BlogEntry> blogEntries = iBlogsRepository.GetAllBlogEntries();
foreach (BlogEntry blogEntry in blogEntries)
{
blogEntry.Url = blogEntry.Title.
Replace("'", string.Empty).
Replace("!", string.Empty).
Replace(" ", "-").
ToLower();
}
return View(blogEntries);
}
Summary
So, we created an MVC project designed by our tests. We only implemented what was required so there was no redundant code which is something that can happen when just cracking out the code.
As I used Moq, I did not have to configure or populate a database or some other repository.
By using Dependency Injection the code would be easily extendable to inject a repository to the Controller using a dependency injector like Ninject.
One gotcha that you may have come across is that if the BlogEntry Url property is set to virtual then the value will not be set in the controller.
I have uploaded the source code for TDD with ASP.NET MVC 3, Moq and Dependency Injection on githib, take a look and fork it.
Thanks for this article!
ReplyDeleteOne thing, there is at least one error:
var mockBlogEntry1 = new Mock()
var mockBlogEntry1 = new Mock(); not new Mock();
DeletePeace
var mockBlogEntry1 = new Mock< BlogEntry>(); not new Mock(< BlogEntry>);
DeletePeace
Thanks Anon, I have updated the blog post.
DeleteAlso I don't understand what this newBlogEntry.Object is.
ReplyDeleteYou are great.. Thank you very much for giving me step by step explanation...
ReplyDeletehow to get the newBlogEntry.Object ?
ReplyDeleteits a typo. it should be like "new mockBlogEntry1.Object" i suppose
ReplyDeleteThanks Michael, I have updated the blog post.
ReplyDeleteTwo errors in example
ReplyDelete1-
blogRepository
.Setup(p => p.GetAllBlogEntries())
.Returns(new List
{
mockBlogEntry1.Object, mockBlogEntry2.Object, mockBlogEntry3.Object
});
2-
const string url2 = "im-still-in-to-this";
Good small example, but what MVC3 has to do with this? I mean if you can't show an httpContext mocked, then purpose of a web application example dies. Just saying its a controller and its a view result.Model, doent mean its a web app mockup.
ReplyDeleteSecond issue is that Title is a little misleading, we never used any Dependency injection, just saying that Ninject CAN be used is nothing, we must have shown at least ONE liner.
Thirdly, in real world db objects go by Chain, maybe blogpost was depending on something, and in web apps, it does matter a lot what are request headers, cookies, parameters and form input..... all of that should be shown in some example.
ReplyDeleteHello Daniel.
ReplyDeleteThank you very much for this tutorial. I found it educational and enjoyable to follow. I would ask if possible can you please show me how to actually use this code. I am still learning MVC and am currently reading dependency injection in .net by Mark Seeman. I have created a view and added some route information in the global.asx file but it still isn't working. Thanks.
tallest 3g base station more excellent webpage http://casinogamesonlinee@blogspot.com
ReplyDeleteWould be nice to fix all those typos in the article above, instead of only on github. I bet many people work through it as they read it... save some frustrations ;-)
ReplyDelete