<= Part 1 | Series Home | Part 3 =>
In our last section, we put together a list of accounts and made sure we could search them in the way our customer wanted. To recap, our customer needed a way to enter in accounts and be able to search them by phone number or name. From a UI perspective, this should be accomplished using one field. Now that we have the searching business logic down, let’s get the UI working so we can show our customer.
UI, meet Business Layer
The sketch for the UI is basically a text field with a button next to it to search and a grid underneath the buttons and textbox to show the results. When the app first starts, the grid should show the list of every customer, sorted by name.
To build a TDDable UI, I typically use a slightly modified form of Fowler’s Presentation Model where I define the exposed actions as an interface, and use the UI screen as a form of Data Transfer Object. To explain, it will probably be best to get writing tests, ey?
The easiest thing to start with is to make sure that the events are getting hooked up correctly. While .NET controls expose a set of events (like Click), I prefer to use event names that are consistent with the domain model. So, for example, the first thing we want to check is that the presenter registers itself as a listener for the SearchCustomersRequested event. This will be the event the UI fires when someone clicks on the button after entering search criteria. I create two new projects – ServiceTrackerPresenter and ServiceTrackerPresenterTests, create a test class called AccountPresenterTests, and write the following test:
[TestMethod]
public void PresenterRegistersAsListenerForSearchCustomersEvent()
{
IAccountView view = new StubAccountView();
AccountPresenter presenter = new AccountPresenter(view);
int expectedListenersCount = 1;
int actualListenersCount =
((StubAccountView)view)
.SearchCustomersRequestedListenerCount;
Assert.AreEqual(expectedListenersCount, actualListenersCount);
}
Something I’m doing here is using the interface declaration, and only casting it to the Stub implementation when I need something special from the Stub that the UI won’t expose. I’ve found that keeps my code honest and prevents mistakes from putting something in the stub that isn’t in the UI or interface.
So let’s get the test compiling. In ServiceAccountPresenter I create two classes – IAccountView and AccountPresenter. The only thing I put in either of them is a constructor in AccountPresenter which accepts an IAccountView. Then, in my AccountPresenterTests.cs, I create the StubAccountView class, having it implement IAccountView. Inside it, I put a property called SearchCustomersRequestedListenerCount, and set it to return 1. Viola! Tests green.
Except that isn’t really what we want here. So I modify the test slightly to make sure it is actually the presenter registering the event:
[TestMethod]
public void PresenterRegistersAsListenerForSearchCustomersEvent()
{
IAccountView view = new StubAccountView();
Assert.AreEqual(0, ((StubAccountView)view)
.SearchCustomersRequestedListenerCount);
AccountPresenter presenter = new AccountPresenter(view);
int expectedListenersCount = 1;
int actualListenersCount =
((StubAccountView)view)
.SearchCustomersRequestedListenerCount;
Assert.AreEqual(expectedListenersCount, actualListenersCount);
}
Ok, now the test fails. So let’s setup our event subscription. First, we need to define the event and delegate in our interface:
namespace ServiceTrackerPresenter
{
public delegate void AccountViewEventDelegate(
object sender, IAccountView view);
public interface IAccountView
{
event AccountViewEventDelegate SearchCustomersRequested;
}
}
And modify our Stub to implement this:
class StubAccountView : IAccountView
{
public event AccountViewEventDelegate
SearchCustomersRequested;
public int SearchCustomersRequestedListenerCount
{
get
{
if (SearchCustomersRequested != null)
{
return SearchCustomersRequested
.GetInvocationList().Count();
}
return 0;
}
}
}
Which gives us a still failing test. Now we just need to modify the behavior of the Presenter:
public class AccountPresenter
{
public AccountPresenter(IAccountView view)
{
view.SearchCustomersRequested += new
AccountViewEventDelegate
(view_SearchCustomersRequested);
}
void view_SearchCustomersRequested(
object sender, IAccountView view)
{
}
}
We aren’t doing much with it, but our test isn’t asking us to. This passes our test, and we run all of our tests just to make sure everything is still looking fine (it is!).
Displaying Accounts using DataBinding
So we now know we can respond to someone clicking the button. It isn’t much, but sometimes it can be hard to figure out what the first test to write is, and for some reason that particular behavior was standing out in my mind. That’s because the real first behavior we have to deal with is displaying Accounts on the page.
Looking again at Fowler’s Presentation model, the form pulls the information from the model. In other words, he has a LoadFromPresentationModel() method in the view which sets up everything in the page. I prefer to expose everything that needs to be set in the interface and let the Presenter set the fields.
So it goes with displaying the Accounts. Because of .NET’s rich DataBinding capability, it will be unlikely that we’d want write our own method to loop through the data and insert it into a grid. We could certainly do that – expose an object in our interface which has an Add() method, and in our presenter loop over the Accounts and call that method. However, I prefer to just use databinding.
So, the next thing we need to specify is that when the form loads, we want to do something, and that something we want to do is set the DataSource of our Domain object that will display the accounts. First, we’ll write a test similar to our last one:
[TestMethod]
p>
public void PresenterRegistersAsListenerForLoadEvent()
{
IAccountView view = new StubAccountView();
Assert.AreEqual(0, ((StubAccountView)
view).AccountViewLoadListenerCount);
AccountPresenter presenter = new AccountPresenter(view);
int expectedListenersCount = 1;
int actualListenersCount =
((StubAccountView)view).AccountViewLoadListenerCount;
Assert.AreEqual(expectedListenersCount, actualListenersCount);
}
And a property to our Stub which returns 0. With the failing test in place, we add the event to our interface, modify our Stub to return the InvocationList count, and then modify our presenter to register as a listener. Green test!
The next thing we want to do is make sure that the DataSource is getting set when the form loads. But the DataSource of what? Obviously we are going to have a Grid of some sort (likely a DataGrid), but to do that, we’d have to have a DataGrid property in our interface, and something about that doesn’t sit right with me.
That leaves us with two options. The first is to write our own interface for data bound controls which exposes the DataSource property, and then have the UIs create a wrapper around the real control which implements the interface (man how I wish .NET used Duck Typing). The second is to have a property of the form itself be set, and rely on the UI authors to do the right thing when they are implementing the form. Some part of me wants to be more deterministic then that, so we’ll try the first method.
Here’s our test:
[TestMethod]
public void AccountGridDataSourceSetDuringViewLoad()
{
IAccountView view = new StubAccountView();
AccountPresenter presenter = new AccountPresenter(view);
Assert.IsNull(view.AccountGrid.DataSource);
((StubAccountView)view).FireAccountViewLoadEvent();
Assert.IsNotNull(view.AccountGrid.DataSource);
}
We’ll make AccountGrid an IAccountGrid interface in our ServiceTrackerPresenter with a DataSource property:
public interface IAccountGrid
{
object DataSource { get; set; }
}
And add AccountGrid to our IAccountView interface. Notice that we are only requiring a get method – it will be unlikely our Presenter will replace an entire DataGrid with a new one, and we can always come back and tweak this later if need be:
IAccountGrid AccountGrid { get; }
Now we’re getting a compiler error that StubAccountView doesn’t implement the IAccountView, so we’ll add AccountGrid to it. Now we’re getting a compiler error that FireAccountViewLoadEvent doesn’t exist in StubAccountView. FireAccountViewLoadEvent is a helper method to raise events that would typically be raised by the UI – in this case, AccountViewLoad. The method is easy to implement:
public void FireAccountViewLoadEvent()
{
if(AccountViewLoad != null)
AccountViewLoad(this, this);
}
Now the compiler is happy. Let’s see about our tests. It fails that AccountGrid is null (which throws a NullReferenceException when we try to access DataSource during the test). What we need is a sensing object to tell when DataSource has been set. So in our AccountPresenterTests we’ll create a StubAccountGrid which implements IAccountGrid and just uses an instance variable to hold DataSource:
class StubAccountGrid : IAccountGrid
{
private object dataSource;
public object DataSource
{
get { return dataSource; }
set { dataSource = value; }
}
}
And in our StubAccountView we’ll use an instance of this class to track what our presenter is doing:
private IAccountGrid accountGrid;
public StubAccountView()
{
accountGrid = new StubAccountGrid();
}
public IAccountGrid AccountGrid
{
get { return accountGrid; }
}
Ok, the compiler is happy, and our tests are now failing that the Assert.IsNotNull failed. So let’s fix that by modifying our presenter to set the data source on load:
void view_AccountViewLoad(object sender, IAccountView view)
{
view.AccountGrid.DataSource = new Accounts();
}
And we have a green test! At this point, some of you may be thinking that setting the DataSource to a new Accounts collection is useless – at best we’ll have a UI that has an empty grid. And you’d be correct – for now. As we write more tests, we should flesh out the DataSource semantics. But to do that, we need a failing test.
Hooking the grid to the DataSource
In fact, that sounds like a good next round of functionality. We have a grid, and a way for a user to tell us that they want to submit criteria, but we don’t have the two hooked up. What we need to do at this point is build the hooks between the user submitting the search criteria, and what gets set to the grid.
So perhaps our next test can look like…Wait a second. Before we start coding the next test, we have some serious duplication happening in our tests. They all seem to start with:
IAccountView = new StubAccountView();
AccountPresenter presenter = new AccountPresenter(view);
However, for our first two tests in this class, we have an assert between the view and presenter instatiations. So we’ll leave it for now, but be mindful of the duplication as we go.
So, the next test for hooking up search with the grid can look something like:
[TestMethod]
public void GridDSSetToUpdatedAccountsWhenSearchEventFired()
{
IAccountView view = new StubAccountView();
AccountPresenter presenter = new AccountPresenter(view);
((StubAccountView)view).FireAccountViewLoadEvent();
view.SearchCriteria = "813-555-1234";
((StubAccountView)view)
.FireSearchCustomersRequestedEvent();
Account expectedAccount =
new Account("Test User", "813-555-1234");
Accounts accounts =
view.AccountGrid.DataSource as Accounts;
Assert.AreEqual(expectedAccount, accounts[0]);
}
I don’t like all that setup just to get a Loaded view, but we’ll tackle that if we need another test. For now we are getting a compiler exception that SearchCriteria doesn’t exist in view, and that FireSearchCustomersRequestedEvent doesn’t exist in our Stub. So we’ll add a SearchCriteria property which returns a string to our interface, and modify our Stub class to add this property and the new event fire method:
public void FireSearchCustomersRequestedEvent()
{
if (SearchCustomersRequested != null)
SearchCustomersRequested(this, this);
}
private string searchCriteria;
public string SearchCriteria
{
get { return searchCriteria; }
set { searchCriteria = value; }
}
Now everything compiles. Our test fails with an index out of range exception – our DataSource is being set to a new instance of Accounts in our presenter. Let’s modify that to hard code some accounts for now. First we’ll modify our listener to call a method to get the Accounts:
void view_AccountViewLoad(object sender, IAccountView view)
{
view.AccountGrid.DataSource = LoadAccounts();
}
private Accounts LoadAccounts()
{
Accounts accounts = new Accounts();
return accounts;
}
And rerun our tests to make sure we didn’t break anything. We didn’t, so let’s finish the LoadAccounts method:
private Accounts LoadAccounts()
{
Accounts accounts = new Accounts();
accounts.Add(new Account("Test User", "813-555-1234"));
accounts.Add(new Account("Justin Example", "813-555-1212"));
return accounts;
}
Now our test is failing that it expected an Account, but got an Account. However, that brings up a question – do two accounts with the same information (Name, PhoneNumber) equal each other? Looking at the Account.cs class, we didn’t override Equals, so we should probably do that first. We comment out the GridDS test we are working on, and create a new AccountTests.cs class in the Servi
ceTrackerLogicTests project. Our first test looks like:
[TestMethod]
public void TwoAccountsWithMatchingNameAndPhoneAreEqual()
{
Account account1 = new Account("Test User", "813-555-1234");
Account account2 = new Account("Test User", "813-555-1234");
Assert.AreEqual(account1, account2);
}
Which fails. So we don’t have that implemented. Let’s go into Account.cs and implement an equals method:
public override bool Equals(object obj)
{
if (obj is Account)
{
Account a = obj as Account;
return a.Name == this.Name
&& a.PhoneNumber == this.PhoneNumber;
}
return false;
}
Which passes our test. We’ll also override GetHashCode and have it return the base.GetHashCode for now.
Back to our Grid. We uncomment our test and run it. This time it passes! WooHoo! Or…is it? We know that we didn’t hook up the events yet, so let’s add another Assert in there:
[TestMethod]
public void GridDSSetToUpdatedAccountsWhenSearchEventFired()
{
IAccountView view = new StubAccountView();
AccountPresenter presenter = new AccountPresenter(view);
((StubAccountView)view).FireAccountViewLoadEvent();
view.SearchCriteria = "813-555-1234";
((StubAccountView)view)
.FireSearchCustomersRequestedEvent();
Account expectedAccount =
new Account("Test User", "813-555-1234");
Accounts accounts =
view.AccountGrid.DataSource as Accounts;
Assert.AreEqual(1, accounts.Count());
Assert.AreEqual(expectedAccount, accounts[0]);
}
This fails that there are two elements in our Accounts collection – it just happened that the item that was the first in the list was the one we were looking for. That’s why it is always important to sanity check your objects as well.
On to getting our test passing. We modify the following method in AccountPresenter:
void view_SearchCustomersRequested(
object sender, IAccountView view)
{
Accounts accounts = LoadAccounts();
accounts = accounts.FindAccount(view.SearchCriteria);
view.AccountGrid.DataSource = accounts;
}
Which passes our tests, meaning we’re almost there. At this point we have our datagrid being loaded with all of the accounts on load, and a user can enter search criteria to filter the grid, and the grid data is filtered by the criteria.
Show me the Data! On a User Interface!
At least, our tests say we do. Up till now, we haven’t touched a UI. For the last part of this section, we’re going to hook up the UI and run through a manual test. Later on we’ll work on automating the UI tests, but for now we have a demo to do.
In our ServiceTracker project, we have a Form1.cs file that’s been sitting there since we started. Let’s rename it to MainForm.cs and look at the code. We see that it has a constructor, but little else. Let’s open the form in the designer and drag a Button, a TextBox and a DataGrid onto the form, naming them SubmitSearch, SearchCriteriaText and AccountsGrid:
Now to start hooking these things up. Go to the code for the form, and have MainForm.cs implement IAccountView. Once you have that wired up, you can right-click on IAccountView and choose Implement Interface in Visual Studio:
This gives us our two events, and the two properties. The first and easiest to implement is SearchCriteria:
public string SearchCriteria
{
get { return SearchCriteriaText.Text; }
set { SearchCriteriaText.Text = value; }
}
In other words, we are delegating the calls directly to the UI elements. Our code behind is basically just a mapper of our domain events to the UI elements that implement them. We do similar things with the two events:
public event AccountViewEventDelegate
SearchCustomersRequested;
public event AccountViewEventDelegate AccountViewLoad;
private void MainForm_Load(object sender, EventArgs e)
{
if (AccountViewLoad != null)
AccountViewLoad(this, this);
}
private void SubmitSearch_Click(object sender, EventArgs e)
{
if (SearchCustomersRequested != null)
SearchCustomersRequested(this, this);
}
Again, mapping the Form.Load and Button.Click events to our domain events. The only thing left is our AccountGrid. We could do this:
public IAccountGrid AccountGrid
{
get { return AccountsGrid; }
}
But the compiler complains that AccountsGrid doesn’t implicitly implement IAccountGrid. We could make it explicit:
public IAccountGrid AccountGrid
{
get { return AccountsGrid as IAccountGrid; }
}
But while the compiler is happy, that would throw a runtime exception. In language like Ruby, this would work because of the principle of Duck Typing, but we have to find a workaround.
In the spirit of DTSTTCPW, we’ll just create a wrapper class in our MainForm.cs class:
class AccountsDataGridWrapper : IAccountGrid
{
DataGridView dgv;
public AccountsDataGridWrapper(DataGridView dgview)
{
dgv = dgview;
}
public object DataSource
{
get { return dgv.DataSource; }
set { dgv.DataSource = value; }
}
}
And modify our AccountGrid implementation to use the Wrapper:
public IAccountGrid AccountGrid
{
get
{
return new AccountsDataGridWrapper(AccountsGrid);
}
}
Now hit F5 and…
Viola! Our UI is hooked up to the presenter with no business logic needing to reside in it. Searching works as well:
Now let’s go demo this baby! We still have a lot of work to do besides automating the UI test – we have the rest of the business logic to go. But we’re moving closer, and we have our tests to support us. And best of all, our customer is seeing real progress at the same time.
Next time we’ll discuss how the demo goes and what part we are going to tackle next.
Cory,
Thanks so much for this great “tutorial” on how-to develop in C# following TDD.
I also like to point a “typo”, in Test GridDSSetToUpdatedAccountsWhenSearchEventFired() of class AccountPresenterTests, where the trailing ; is missing: view.SearchCriteria = “813-555-1234”
Regards
Great catch! I actually found two places where I had missed semicolons. You likely won’t see that later in the series as I changed the way I get code into the article. But thanks for the feedback, and I’m glad you are enjoying the article.
Cory,
I apologize for my lack of knowledge in C#, but after ending Part 2 I cannot get the same screenshots as those you’ve posted. The AccountsGrid doesn’t show anything and even after clicking on Search button the DataGridView remains grey.
Can you help me, if you can spare the time, please?
Thanks.