<= Part 3 | Series Home | Part 5 =>
11/14/07 – Updated to fix a problem where Windows Live Writer didn’t upload the whole article
At the end of the last post, we had automated tests in place that showed our business logic correctly triggered a window to open, and that the UI implemented the logic correctly. However, that window didn’t do much of anything – especially not add accounts to the Accounts collection. Let’s get that fixed!
Reviewing Our Progress
Right now, our AccountPresenter class is doing everything it can to make sure AddAccount is correctly called and hooked up. So we can now move to implementing the business logic in AddAccountView. There are two pieces – validating the fields when a user submits the form, and correctly creating an Account instance and calling back to the AccountPresenter to add it. It looks something like:
So our next tests are going to be on the AddAccountView side. I create a new test class called AddAccountPresenterTests.cs in my ServiceTrackerPresenterTests project. Since we’ll have a submit button on the form, we should make sure we are listening for it. Since the button will be used to make sure the Account information is valid, and then create the Account and pass it back to the caller, we’ll call it AccountReadyForCreation perhaps? Any thoughts on better names?
Being A Good Listener
Copying over the similar test from AccountPresenter, we get:
[TestMethod]
public void PresenterRegistersAsListenerForAccountReadyForCreation()
{
IAddAccountView view = new StubAddAccountView();
Assert.AreEqual(0, ((StubAddAccountView)
view).AccountReadyForCreationListenerCount);
AddAccountPresenter presenter =
new AddAccountPresenter(view);
int expectedListenersCount = 1;
int actualListenersCount =
((StubAddAccountView)view)
.AccountReadyForCreationListenerCount;
Assert.AreEqual(expectedListenersCount, actualListenersCount);
}
However, one of the feedback items I got from Brian Button was that I really have two test cases there – one for the zero case, and one for the listener case. We had a good discussion on the TDD list about it, and two solutions came from it:
- Set the AccountReadyForCreationListenerCount in the test so that we know what it is
- Use a method to make it clear what we are checking – something like SanityCheckStub()
However, I like the sanity check as is, especially since the get method is checking the invocation list. But it is something to keep in mind for later tests, and something I may move to SetUp if it gets too noisy.
I get the test compiling by adding the StubAddAccountView class to the AddAccountPresenterTests.cs file and having it implement IAddAccountView. I also give it a property called AccountReadyForCreationListenerCount which is just returning 0 for now. Finally, I create an AddAccountPresenter.cs in the ServiceTrackerPresenter project and give it a constructor which takes in an IAddAccountView. Running the tests gives us the predictable message that the count is 0 when we expected 1, so let’s get that working. First, we’ll add the AccountReadyForCreation event to the interface, which forces the add to the Stub class:
public delegate void AddAccountViewEventDelegate(object sender, IAccountView view);
public interface IAddAccountView
{
event AddAccountViewEventDelegate AccountReadyForCreation;
}
class StubAddAccountView : IAddAccountView
{
public event AddAccountViewEventDelegate AccountReadyForCreation;
public int AccountReadyForCreationListenerCount
{
get { return 0; }
}
}
Now we can modify our presenter to listen for the event:
public class AddAccountPresenter
{
public AddAccountPresenter(IAddAccountView view)
{
view.AccountReadyForCreation += new AddAccountViewEventDelegate(view_AccountReadyForCreation);
}
void view_AccountReadyForCreation(object sender, IAccountView view)
{
}
}
And finally, modify our test to make sure it is checking the invocation list:
public int AccountReadyForCreationListenerCount
{
get
{
if (AccountReadyForCreation != null)
{
return AccountReadyForCreation.GetInvocationList().Count();
}
return 0;
}
}
Which gives us our green test. Now what we need to happen is that when the event is fired, the data is validated, a new Account object is created, and a callback method on the presenter passed to the AddAccount form needs to be called with this new Account so it can be added. However, we seem to be having a problem having a clear vision of the next test, which means we probably have a design flaw somewhere. So let’s step back for a second.
Reviewing Our Design
In the scheme of things, the workflow for what we are doing is that the user pushes a button to add a new account, enters the account information, and clicks save. When they click save, the form should validate the information, and if it is valid, create an account object and pass it back to the AccountView presenter to add it to the Accounts collection, which will then update the UI. So our tests would be:
- A valid account is added to the accounts collection
- A user clicking submit with an invalid field should get a warning message (with tests for each field that could be invalid)
That helps clarify things, at least for me. We need our submit action to do two separate things – validate the information, and pass the account to the accounts collection. And maybe that’s the rub – neither the presenter or the view seem like the best place to create the account and validate – that seems like the responsibility of the Account class. It should be able to create itself and validate that the fields are, well, valid. I’m envisioning a way to pass the AddAccount form to the Account class to have it create an Account object. Using this, Account can pull what it needs from the view to construct and validate the account data, and then the presenter is responsible for passing the form over, and handling the result. So now we’ll need the following:
- The presenter creates an Account class on submit by passing the view to a factory method in the Account class
- The presenter passes the Account to the callback on successful validation
- The presenter closes the form on succesful validation
- The presenter leaves the form open on validation problems
So writing our first test gives us this mess:
[TestMethod]
public void PresenterPassesAccountObjectToCallbackWhenValid()
{
Account fakeAccount = new Account("Justin Example", "813-555-1212");
account.IsValid = true;
StubAccountPresenter fakeAccountPresenter = new Stu
bAccountPresenter();
StubAddAccountView fakeView = new StubAddAccountView();
fakeView.Presenter = fakeAccountPresenter;
AddAccountPresenter presenter = new AddAccountPresenter(fakeView);
presenter.ProcessAccount(fakeAccount);
Assert.AreEqual(fakeAccount, fakeAccountPresenter.AccountToBeAdded);
}
Remember that in our last part, we ended by passing the AccountPresenter as a parameter to AddAccountView so that the Account could be added to the Accounts collection. That seems unwise now – we really should just have a DataSource that we pass around that also notifies interested parties when it has been updated. Let’s delete the above test for now and fix that.
Passing Notes During Class Is Bad
The first thing we need to change is the PresenterRequestsNewWindowWithItselfWhenAddAccountRequested test in AccountPresenterTests. It should check to make sure the DataSource – the Accounts collection – was passed in. The problem is that we get Accounts from the LoadAccounts() method in AccountPresenter, and don’t hold a reference to it. What we’ll do is modify the Accounts collection to be able to load and hold the accounts in it, and use that within the form. The change moves the code from LoadAccounts into Accounts, and then calls that:
public class Accounts : List
{
private static Accounts accountStore;
public static Accounts AccountStore
{
get
{
if (accountStore == null)
{
accountStore = new Accounts();
accountStore.Add(new Account("Test User", "813-555-1234"));
accountStore.Add(new Account("Justin Example", "813-555-1212"));
}
return accountStore;
}
}
//...
}
public AccountPresenter
{
//…
private Accounts LoadAccounts()
{
return Accounts.AccountStore;
}
}
With that change made, let’s run our tests. All green.
Updating The Grid
Next, we need to make sure the DataGrid gets updated when the Accounts collection is updated. First, let’s make sure that our DataSource and AccountStore match:
[TestMethod]
public void DataGridSourceMatchesAccountsCollectionOnLoad()
{
IAccountView view = new StubAccountView();
AccountPresenter presenter = new AccountPresenter(view);
((StubAccountView)view).FireAccountViewLoadEvent();
Assert.AreEqual(
Accounts.AccountStore,
view.AccountGrid.DataSource);
}
Green test. Now to make sure it is updated:
[TestMethod]
public void DataGridUpdatedWhenAccountsCollectionChanged()
{
IAccountView view = new StubAccountView();
AccountPresenter presenter = new AccountPresenter(view);
((StubAccountView)view).FireAccountViewLoadEvent();
Accounts.AccountStore.Add(new Account("Faked Out", "813-111-2222"));
Assert.AreEqual(Accounts.AccountStore, view.AccountGrid.DataSource);
}
And…another green test? Having worked with DataGrids before, I know it isn’t that easy, and it probably a side effect of using a stub. I try the following test in our MainFormTests:
[TestMethod]
public void DataGridUpdatedWhenAccountsCollectionChanged()
{
StubMainForm view = new StubMainForm();
StubAccountPresenter presenter = new StubAccountPresenter(view);
view.FireLoadEvent();
Accounts.AccountStore.Add(new Account("Faked Out", "813-111-2222"));
Assert.AreEqual(Accounts.AccountStore, view.AccountGrid.DataSource);
}
But it passes as well. Let’s try one more test:
[TestMethod]
public void RowsDisplayedUpdatesWhenAccountsCollectionChanged()
{
using (StubMainForm view = new StubMainForm())
{
view.Show();
DataGridView realGrid = view.GetAccountsGrid();
Assert.IsNotNull(realGrid, "realGrid");
Assert.AreEqual(2, realGrid.RowCount);
Accounts.AccountStore.Add(new Account("Faked out", "813-111-2222"));
Assert.AreEqual(3, realGrid.RowCount);
view.Close();
}
}
Basically we are accessing the actual AccountsGrid object in the UI and checking to make sure the RowCount was updated when the DataSource changed. To make this compile, we had to widen the visibility of AccountsGrid in MainForm.cs to protected, and then added a method in the StubMainForm called GetAccountsGrid which returns it. This test fails that the row count was not updated. In other words, the DataSource instance gets updated, but that doesn’t mean the UI gets repainted. To fix this, we’ll need to do two things – one, have our Accounts have a way to notify us when it has been updated, and then have our presenter listening for this event and updating the datasource of the grid when this happens.
Since we’ll need the former before we can have the latter (which is what the test above is showing), let’s comment this test out and write a test which expresses what we need Accounts to do.
Just Wake Me If You Need Anything
In our AccountsTests in ServiceTrackerLogicTests, we add the following:
[TestMethod]
public void AccountsCollectionNotifiesListenersWhenAdded()
{
Accounts accounts = initializedAccounts;
bool updatedFired = false;
accounts.Updated += delegate { updatedFired = true; };
accounts.Add(new Account("Blah", "555-111-1212"));
Assert.IsTrue(updatedFired);
}
Which doesn’t compile because Updated isn’t an event. So we add the following to Accounts:
public event EventHandler Updated;
And we have a compiled, red test. We make it pass by adding the following to Accounts:
public new void Add(Account item)
{
base.Add(item);
if(Updated != null)
Updated(this, null);
}
But, looking at this, it isn’t ideal. using the new operator is fine if you are always working with instances of that type. But what if someone uses Accounts as a List? Besides, we can’t be the only ones who want an updatable list. Indeed, .NET 3.0 introduces an ObservableCollection. Let’s see if we can just swap it out. We change the Accounts class to:
public class Accounts : ObservableCollection
To see ObservableCollection, we had to add a reference to Windowsbase.dll. We also had to change the constructors we were using in Accounts, and modify the FindAccounts method (since FindAll isn’t in ObservableCollection). Once we got it compiling, it caused a cascade of compiler errors that we didn’t have Windowsbase.dll in our other projects that used Accounts. Fixing that got us a clean compile, and running all of our tests shows we didn’t break anything. If that doesn’t show the power of TDD, I don’t know what would.
We do have a test we have to fix. Our AccountsCollectionNotifiesListenersWhenAdded test relies on there being an Updated event, and using the shadowed Add method. Let’s change that to use the CollectionChanged event, and comment out the add method we added:
[TestMethod]
public void AccountsCollectionNotifiesListenersWhenAdded()
{
Accounts accounts = initializedAccounts;
bool updat
edFired = false;
accounts.CollectionChanged += delegate { updatedFired = true; };
accounts.Add(new Account("Blah", "555-111-1212"));
Assert.IsTrue(updatedFired);
}
And it stays green. So now we have an ObservableCollection in place, we can go back to our rows displayed problem. Let’s put our test back in place:
[TestMethod]
public void RowsDisplayedUpdatesWhenAccountsCollectionChanged()
{
using (StubMainForm view = new StubMainForm())
{
view.Show();
DataGridView realGrid = view.GetAccountsGrid();
Assert.IsNotNull(realGrid, "realGrid");
Assert.AreEqual(2, realGrid.RowCount);
Accounts.AccountStore.Add(new Account("Faked out", "813-111-2222"));
Assert.AreEqual(3, realGrid.RowCount);
view.Close();
}
}
Rerunning the test shows it is still failing. That’s important because you never know if a change you made previously made this test pass. For example, it’s hard to know if the GUI is wired to automatically listen for updates to the DataSource if the DataSource is an ObservableCollection. Since it isn’t, we need to wire it up ourselves. So in our presenter, we can add the following line in our constructor:
Accounts.AccountStore.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(AccountStore_CollectionChanged);
However, we find that we aren’t holding a reference to the view currently in the presenter – mainly because we’ve been working primarily on callbacks. So I add an instance variable for the view, and implement AccountStore_CollectionChanged as:
void AccountStore_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
accountView.AccountGrid.DataSource = Accounts.AccountStore;
}
That should allow our test to pass, but it doesn’t. Knowing some quirks about UI elements, let’s try the following first:
void AccountStore_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
accountView.AccountGrid.DataSource = null;
accountView.AccountGrid.DataSource = Accounts.AccountStore;
}
Sure enough, that lets our test pass. Now, to the casual observer, it may not be clear why we are doing this. So we are going to add something you haven’t seen up till now – a comment:
//Must set DataSource to null first or else
//the GUI won't recognize the change
This test is a great case where, even using the MVP pattern, it is important to be sure you have acceptance tests exercising the real GUI.
Seriously. Can We Add An Account Now?
Now lets run our full test suite – 30 out of 30 passed. Good to go! Let’s get the Account add working. Remember that what led us down the path we just went was that we were passing a reference to the AccountPresenter to AddAccountView. Then we were going to have to find a way to have AddAccountPresenter callback to AccountPresenter to update the Account grid. However, now all we need to do is have AddAccountPresenter update the Accounts collection, and AccountPresenter will handle updating the GUI. Therefore this test in AccountPresenterTests:
[TestMethod]
public void PresenterRequestsNewWindowWithItselfWhenAddAccountRequested()
{
IAccountView view = new StubAccountView();
AccountPresenter presenter = new AccountPresenter(view);
((StubAccountView)view).FireAddAccountRequestedEvent();
Assert.AreEqual(presenter, ((StubAccountView)view).PresenterForOpenAccountWindow);
}
Isn’t correct. What we need was the test we originally had here which just opens the window as a dialog. The test from Part 3 was:
[TestMethod]
public void PresenterRequestsNewWindowFromViewWhenAddCustomerRequested()
{
IAccountView view = new StubAccountView();
AccountPresenter presenter = new AccountPresenter(view);
((StubAccountView)view).FireAddCustomerRequestedEvent();
Assert.IsTrue(((StubAccountView)view).NewCustomerWindowRequested);
}
which was superseded by the PresenterRequestsNewWindowWithItselfWhenAddAccountRequested test. Let’s swap those tests. To do that, we’ll first put in the old test and make sure it passes. It doesn’t compile, because we changed the name to FireAddAccountRequestEvent. NewCustomerWindowRequested (which should be NewAccountWindowRequested) was removed, so we add back in the instance variable and change the stub to set that when OpenAccountWindow is called (leaving in the old code for now). That lets the test compile, and pass.
Now we can remove the old test. The easiest thing it to remove the passing of the parameter in the OpenAccountWindow defintion in the interface, and then lean on the compiler to find the places we need to fix. So we change IAccountView’s definition to:
void OpenAccountWindow();
And we clean up MainForm and StubAccountView, as well as modify AccountPresenter to clean all of that up. We also remove the PresenterForOpenAccountWindow property we exposed in StubAccountView. Finally, we remove the PresenterRequestsNewWindowWithItselfWhenAddAccountRequested test since it can’t function anymore. Well, actually, then we finally run all of our tests to prove we haven’t broken anything – and we haven’t!
So, finally we can go back and get the AddAccountView adding accounts. Looking at our AddAccountPresenterTests, the only thing we have it doing is listening for the AccountReadyForCreation event. But now, instead of our presenter having to do all kinds of crazy callbacks, it just needs to create an Account from the information on the form, validate it, and add it to the Accounts collection. That’s much better.
Letting The Account Deal With Itself
At the beginning of the article, we talked about Account having a factory method it could use to create Account instances from the form. The easiest way would be to create an interface – IAccount – which defines the contract for an Account, and then have the factory method spit out an Account from there. It would be nice if the AddAccountView could be an Account, and there certainly may be better ways. So let’s drive out a factory method in the Account class:
[TestMethod]
public void FactoryMethodCreatesAccountWithCorrectInformation()
{
IAccount account = new Account("My User", "813-111-2222");
account.Address = "12345 Blah Circle"
account.City = "Tampa"
account.State = "FL"
account.ZipCode = "12345"
Account createdAccount = Account.CreateAccount(account);
Assert.AreEqual(account as Account, createdAccount);
}
It doesn’t compile because neither IAccount nor CreateAccount exist. We’ll tackle IAccount first. In our ServiceTrackerLogic project we create:
public interface IAccount
{
string Name { get; set; }
string PhoneNumber { get; set; }
string Address { get; set; }
string City { get; set; }
string State { get; set; }
string ZipCode { get; set; }
}
Now we get an error that we can’t cast IAccount to Account – presumably because Account doesn’t implement IAccount. We fix that with:
public class Account : IAccount
Now we get that CreateAccount doesn’t exist. We’ll just create the static method and have it return a new Account(). Now we can run our test, see it fail, and fix it by changing CreateAccount to:
public static Account CreateAccount(IAccount account)
{
Account a = new Accoun
t(account.Name, account.PhoneNumber);
a.Address = account.Address;
a.City = account.City;
a.State = account.State;
a.ZipCode = account.ZipCode;
return a;
}
We could also use the new C# syntax to do:
Account a = new Account(account.Name, account.PhoneNumber) {
Address = account.Address
//...
};
But we’ll leave it as is for now. Now our test passes, meaning we have the plumbing in place. We now have two options. We can either have AddAccountView implement IAccount – or have IAddAccountView inherit from it. I prefer the latter, which looks like:
public interface IAddAccountView : IAccount
Doing that causes a host of compiler errors since neither our AddAccountForm nor our StubAddAccountView expose those properties. Let’s fix that. For the Stub, we’ll just expose the properties to return empty strings:
public string Name
{
get { return String.Empty; }
set { }
}
//etc, etc
For our actual form, we’ll add the fields to the form using the designer:
Each of the fields is named FieldNameText – for example, PhoneNumberText. This is because the interface requires us to implement PhoneNumber as a property returning/receiving a string. So to implement the interface, we now need to add the properties. However, something interesting happened. We clicked on the interface declaration in AddAccountForm and told Visual Studio to implement the interface. It added all of the fields except Name. Wondering why, we checked in IAccount – but it was there. Then it hit us – AddAccountForm is a Form which exposes a Name property. The only real option we have is to modify our Account class to call it AccountName. So we go into IAccount and do a Refactor->Rename to AccountName, and clean up the references. We also run our tests for good measure – everything’s still green.
To implement the interface in the Form, the properties just map the interface property to the text property of the control. It looks like:
public string AccountName
{
get { return NameText.Text; }
set { NameText.Text = value; }
}
Passing Accounts In Class Is Good
With all of these changes in place (and our tests all green) we can now write the next test:
[TestMethod]
public void ValidAccountGetsAddedToAccountsOnSubmit()
{
IAddAccountView view = new StubAddAccountView();
view.AccountName = "Testing Account"
view.PhoneNumber = "813-555-1212"
AddAccountPresenter presenter = new AddAccountPresenter(view);
((StubAddAccountView)view).FireAccountReadyForCreationEvent();
Assert.AreEqual(3, Accounts.AccountStore.Count);
Assert.IsTrue(Accounts.AccountStore.Contains(new Account("Testing Account", "813-555-1212")));
}
We have to fix the compiler error that FireAccountReadyForCreationEvent is not implemented. In implementing the method, we’re getting a strange error that the Stub form can’t be cast to the interface. We can clearly see that the stub implements the interface. Looking at the delegate declaration we see we did:
public delegate void AddAccountViewEventDelegate(object sender, IAccountView view);
Instead of:
public delegate void AddAccountViewEventDelegate(object sender, IAddAccountView view);
Making that change makes everything better. Now our test compiles, and running it gives us that Accounts.AccountStore has 2 elements when we are expecting 3. To fix this, we modify the Presenter to act on the event:
void view_AccountReadyForCreation(object sender, IAddAccountView view)
{
Accounts.AccountStore.Add(Account.CreateAccount(view));
}
Now that’s nice and easy. It doesn’t take into account validation or anything yet, but I think it will make this test pass nicely. In fact, part of our test passes nicely. There are now 3 objects in the collection, but the Contains test is not passing. We’ll change the test to use:
[TestMethod]
public void ValidAccountGetsAddedToAccountsOnSubmit()
{
IAddAccountView view = new StubAddAccountView();
view.AccountName = "Testing Account"
view.PhoneNumber = "813-555-1212"
AddAccountPresenter presenter = new AddAccountPresenter(view);
((StubAddAccountView)view).FireAccountReadyForCreationEvent();
Assert.AreEqual(3, Accounts.AccountStore.Count);
Account foundAccount =
Accounts.AccountStore.FindAccount("Testing Account")[0];
Assert.IsNotNull(foundAccount);
}
Which also fails. Let’s try something different:
Account foundAccount = Accounts.AccountStore[2];
Assert.AreEqual("Testing Account", foundAccount.AccountName);
Which fails with it expecting “Testing Account” but getting “”. Of course! The stub class we’re using didn’t implement the properties as actual instance variables. They get changed to:
string accountName;
string phoneNumber;
//...
public string AccountName
{
get { return accountName; }
set { accountName = value; }
}
public string PhoneNumber
{
get { return phoneNumber; }
set { phoneNumber = value; }
}
//…
Our test passes now, so we switch it back to the original contains:
Assert.IsTrue(Accounts.AccountStore.Contains(new Account("Testing Account", "813-555-1212")));
which now passes.
Failure Is Not An Options
We run all of our tests and…failure? Looking at the test list, the test we just ran failed. It’s saying it expected 3, but was 4. Aha! Our test has a side effect – it’s modifying the collection, which is screwing everything else up. We can fix that by putting the following in the test:
Accounts accountsBeforeTest = Accounts.AccountStore;
//...
Accounts.AccountStore = accountsBeforeTest;
but AccountStore is read-only. This is a very important issue – tests should never leave behind cruft that could cause other tests to fail. The simplest thing would be to create a setter, and we’ll run with that for now. This will probably come up again as we add persistance to Accounts, but right now we need to get past the red test. So we add the setter, and the code from above. However, we will watch closely and if we add another test which modifies the collection we’ll move that code to the Setup/Teardown.
So we run our tests and…still red? Same error message too. So another test must be modifying the collection. In fact, the very first test in the list is DataGridUpdatedWhenAccountsCollectionChanged – I bet that’s the culprit. And indeed, we are adding an account there. Let’s add the same lines before, but something is just not smelling right with this. Turns out we are going to have to address it now – because that didn’t let our test pass.
What we should be doing is working on a known collection. So instead of getting the value, setting it to what we expect. Another way would be just to set AccountStore to null at the end of the test since we lazy load it back up on the get method. However, we need to find all of the tests that Add something to the collection. We do a search for “AccountStore.Add” and find three tests that add something to the collection. Adding the Accounts.AccountStore = null line to all three locations lets the tests pass.
Since this is something that needs to be done at the end
of the test, I’ll add TearDown methods for both test classes that had the problem – MainFormTests and AccountPresenterTests. It looks like:
[TestCleanup]
public void TearDown()
{
//Clean up AccountStore
Accounts.AccountStore = null;
}
With that in place, we rerun the tests, and they are all still passing. So we remove out setting the AccountStore to null in each of the tests and rerun. Still green, so it seems safe to move on. It does make me start to wonder if perhaps a different way of organizing the tests might have made that easier. But we’ll save that for later.
Wiring Up the UI
Ok, so the last test we wrote showed that on Submit the Presenter will create an Account using the factory method, passing the view in, and add that to the Accounts collection. Let’s get that working on the UI itself. We add a new test class called AddAccountFormTests.cs to the ServiceTrackerTests project. The first thing we need to check is that the form is calling AccountReadyForCreation when the AddAccount button is clicked:
[TestMethod]
public void AccountReadyForCreationFiredOnAddAccountClick()
{
using (AddAccountForm addAccountForm = new AddAccountForm())
{
bool accountReadyForCreationFired = false;
addAccountForm.AccountReadyForCreation += delegate { accountReadyForCreationFired = true; };
addAccountForm.Show();
addAccountForm.AddAccount.PerformClick();
Assert.IsTrue(accountReadyForCreationFired);
}
}
We have to go make AddAccount public for this to compile, which we do. Running the test fails, so we need to go implement this. So we add the following to AddAccountForm:
private void AddAccount_Click(object sender, EventArgs e)
{
if (AccountReadyForCreation != null)
AccountReadyForCreation(this, this);
}
Which passes our test. Next, let’s make sure the Account is making it into the collection when we fire AccountReadyForCreation:
[TestMethod]
public void ValidAccountAddedToCollectionOnAddAccountClick()
{
using (AddAccountForm addAccountForm = new AddAccountForm())
{
addAccountForm.Show();
addAccountForm.AccountName = "Form Testing"
addAccountForm.PhoneNumber = "555-112-3344"
addAccountForm.AddAccount.PerformClick();
}
Assert.IsTrue(Accounts.AccountStore.Contains(new Account("Form Testing", "555-112-3344")));
}
This test tells us two things. First, it doesn’t work. Second, we’ll be modifying the Accounts collection, so we need our TearDown method. So what would cause this not to work? If the presenter isn’t aware of the view. And sure enough, looking at the code behind, nowhere do we hook the two together. So we add the following to AddAccountForm:
private AddAccountPresenter presenter;
public AddAccountForm()
{
InitializeComponent();
presenter = new AddAccountPresenter(this);
}
And now our test passes! Let’s run them all to make sure no crud bugs are around…and all green.
You Don’t Have To Go Home (But You Can’t Stay Here)
So we’ve got one more test to write. If the Account is valid (which they all are right now) then the form should close. So we need to test that the presenter calls close on the view. We go over to AddAccountPresenterTests and see that we didn’t add the TearDown here. We do that, clean up the test, and rerun all tests. Good to go. Marching on, we add the following test:
[TestMethod]
public void PresenterCallsCloseAfterValidAccountAdded()
{
IAddAccountView view = new StubAddAccountView();
view.AccountName = "Testing Account"
view.PhoneNumber = "813-555-1212"
AddAccountPresenter presenter = new AddAccountPresenter(view);
((StubAddAccountView)view).FireAccountReadyForCreationEvent();
Assert.IsTrue(((StubAddAccountView)view).CloseWasCalled);
}
Now we have to add CloseWasCalled to our stub. Adding the instance variable was easy enough, but what is missing is that our interface doesn’t have a close method. So we add that. We then implement Close in our stub by having it set our instance variable. Note that we don’t need to add anything to AddAccountForm – it has a Close method already. Now that our test compiles we run it – and it fails. So let’s fix that by changing the presenter:
void view_AccountReadyForCreation(object sender, IAddAccountView view)
{
Accounts.AccountStore.Add(Account.CreateAccount(view));
view.Close();
}
And sure enough, our close method is called. Finally, let’s make sure our form is being closed in our AddAccountFormTests:
[TestMethod]
public void FormClosedAfterValidAccountAddedFromClick()
{
using (AddAccountForm addAccountForm = new AddAccountForm())
{
addAccountForm.Show();
addAccountForm.AccountName = "Form Testing"
addAccountForm.PhoneNumber = "555-112-3344"
addAccountForm.AddAccount.PerformClick();
Assert.IsFalse(addAccountForm.Visible);
}
}
And a green test it is. Just to be picky, I add an Assert.IsTrue(addAccountForm.Visible) right after I call Show, and the test still passes, so I pull it back out. We run all of our tests, and we’ve got a green light to go.
We’ve done a lot of coding and testing, but are they really giving us a true view to what is going on? If so, we should be able to start up the application, hit the Add Account button, have a window pop up, enter some information, hit the Save button, see the window close, and see our entry in the DataGrid. Without further ado..
After F5:
Hitting the Add Account button brings up the form:
And entering in some information and hitting Add Account closes the window and gives us:
So yes, we really can trust the tests. That’s welcome news!
We still have some validation left, since Account name and Phone number are required, but this seems like a good stopping point for now.
To help aid in the process of following along, I’m going to be including the code downloads at the bottom of each part. I’m using MSTest and VS2008 if you are following along at home.
Any feedback is most welcome!
In step “Being A Good Listener” I had to add the following statement to AddAccountForm.cs:
public event AddAccountViewEventDelegate AccountReadyForCreation;
Although #Develop after that issues a warning saying that “The event ‘TDD_WinFormApp.AddAccountForm.AccountReadyForCreation’ is never used (CS0067)”.
In order to reach final sub-step of Updating The Grid I had to change the statement in [TestMethod] RowsDisplayedUpdatesWhenAccountsCollectionChanged
from:
DataGridView realGrid = view.GetAccountsGrid();
to:
DataGridView realGrid = view.GetAccountsGrid;
Meaning, make it call a property instead of a method. Does this make any sense to you, or am I doing anything wrong?
Regards.