Saturday, May 23, 2009

Revving up the TDD engine

tach

I’m still in the early stages of writing unit tests at work… but I did commit 26 passing tests to source control this afternoon!  The pain is fading, I’m gaining confidence and speed and I’m getting excited about the number of tests I’ve written.  We’re no mature TDD shop yet, but it is an enjoyable stage when the shiny new unit tests still make us go “ah”.

In this post I’ll finish out the TDD section of my NCharlie project series by walking through some code that tests the interaction between a controller and view.  It includes using Rhino Mocks to raise an event and verify that the controller calls the correct method on the view in response.  I know in my last post I practically gave up on Rhino Mocks in favor of writing my own mock classes and methods… well, I changed my mind.  RM is a great tool, and I’m getting used to the syntax required for .Net 2.0 projects.  So here goes…

search-results

I want to test the behavior when a user clicks one of the “[show]” links in the Task Search results list.  The application should show the Task Detail view when this happens:

task-detail

So I’ll name my test accordingly:

[Test]
public void Should_call_show_task_when_task_selected_event_is_raised()
{
}


Before I start into the body of this test method, there is some setup code that I need to get in place:



[TestFixture]
public class TaskManagerControllerTests
{
MockRepository mocks;
ITaskManagerView viewMock;
ITaskRepository repository;

[SetUp]
public void SetUp()
{
mocks = new MockRepository();
viewMock = mocks.DynamicMock<ITaskManagerView>();
repository = mocks.CreateMock<ITaskRepository>();
}



The mock view will allow me to test my controller code without needing to have a view implementation (ASPX page) wired up and working first.  First I arrange the needed elements for the test by creating the event arguments object that will be passed with the event and an event raiser named “clickEvent”:



[Test]
public void Should_call_show_task_when_task_selected_event_is_raised()
{
string taskID = "12345678-1234-1234-1234-123456789012";
TaskSelectedEventArgs e = new TaskSelectedEventArgs(taskID);

IEventRaiser clickEvent = Expect
.Call(delegate { viewMock.TaskSelected += null; })
.IgnoreArguments()
.GetEventRaiser();



Next I use the Rhino mocks “Expect.Call” method to state that I expect the view’s “ShowTask” method to be called with with my test task ID as the parameter.  The expectations are finished with a call to the R.M. “ReplayAll” method:



[Test]
public void Should_call_show_task_when_task_selected_event_is_raised()
{
string taskID = "12345678-1234-1234-1234-123456789012";
TaskSelectedEventArgs e = new TaskSelectedEventArgs(taskID);

IEventRaiser clickEvent = Expect
.Call(delegate { viewMock.TaskSelected += null; })
.IgnoreArguments()
.GetEventRaiser();

Expect.Call(delegate { viewMock.ShowTask(e.TaskID); })
.Repeat.Once();
mocks.ReplayAll();
}



Finally, I execute the code that will test this behavior… I instantiate a new TaskManagerController (this happens when the view is created) and raise the click event.  The R.M. “VerifyAll” method verifies that all my expectations are met when the code executed:



[Test]
public void Should_call_show_task_when_task_selected_event_is_raised()
{
string taskID = "12345678-1234-1234-1234-123456789012";
TaskSelectedEventArgs e = new TaskSelectedEventArgs(taskID);

IEventRaiser clickEvent = Expect
.Call(delegate { viewMock.TaskSelected += null; })
.IgnoreArguments()
.GetEventRaiser();

Expect.Call(delegate { viewMock.ShowTask(e.TaskID); })
.Repeat.Once();
mocks.ReplayAll();

new TaskManagerController(viewMock, repository);
clickEvent.Raise(null, e);
mocks.VerifyAll();
}


That’s it.  The code to make it pass is painfully simple (painful because the code to test it is so much longer… I’m getting less and less offended by this state of affairs though, since the tests look like this for complicated code under test as well.  Besides, these tests copy and paste very easily!)  Here is the code from the controller under test:



private void OnTaskSelected(object sender, TaskSelectedEventArgs e)
{
view.ShowTask(e.TaskID);
}


And that makes it pass:



tests-passed



 



 

No comments:

Post a Comment