This is part two of the blog series about creating a WPF application using TDD to drive the functionality and MVVM to split that functionality away from the view into something more testable. You may want to read part one if you haven’t already.
In this part, we’ll see what it takes to display data on the front page about each player on the softball team. We’re not going to display all their stats, since we don’t need to for the example, but it would be very easy to extend this to do so. (N.B. I have a more full version of this that I’ll post that includes a database backend with more data in it than what I’m going to show here. The data backend is irrelevant to the layer being discussed here, so I’m going to ignore those details.)
The end result
At the end of this process, what we’re going to have is a list of players and their numbers. We could easily extend it to total stats for each of them, but that’s outside the point of the example, and is left as an exercise for the reader (I hated when college textbooks said that!).
So anyhow, you can see the 4 players listed and their numbers. This data came from the data repository that I created behind the view, in a class called PlayerRepository. I’m not going to go into the details of how this repository class was created, as the database layer is outside the scope of this article, so we’ll just assume that we created it when it was needed, and I won’t include discussion or tests for it. The important part of us to think about is how the data got into the view model and how it got from there onto the view. And that begins our (short) story.
Step 1 – Get data to the view
In order to get data to the view, the view model needs to expose an ObservableCollection that the view can bind to. I decided to create a property called Players on the TeamStatControlViewModel, driving it through tests. I created a test fixture in the ViewModelFixtures assembly called TeamStatControlPlayersDataBindingFixture, and I put my test into there (as an aside, I’m trying a different style of fixtures here, where I’m naming fixtures after behaviors or features and putting all the tests for those features in that fixture. I’m intentionally trying to break out of the app class <-> fixture class model and create tests around the behaviors of the system, regardless of where those behaviors may lie. Let me know if you like it, please.)
Step 2 – Get the data to the view model
Now that we have the data available to be shown on the view, we need to provide that data to the view model. We do this by giving the view model access to the PlayerRepository we spoke of earlier and letting it query the repository for the data as needed. Again, very simple. Here is the test in that same test fixture:
The test start driving to define the behavior our system is going to need. We already know that the view model is going to need a reference to the repository, so we add the repository to the constructor arguments for the view model on line 5. (I like working backwards in a situation like this to discover the objects needed. I’ll write the constructor signature first and use that to drive object creation on previous lines, like you see here.) This forces us to create a mock version of the repository, which we do in line 4 by discovering an IPlayerRepository interface, details to be fleshed out. On line 6, we set up the behavior that we want the view model to invoke on the repository, which is the GetAllPlayers method, which for our purposes needs to return an empty collection of the appropriate type. Finally, we invoke the Players property and verify that the repository’s GetAllPlayers method is indeed called.
In getting this to compile, we create the IPlayerRepository interface and modify the signature of TeamStatControlViewModel to take the new IPlayerRepository parameter. Run test, test fails, and we finally implement:
At this point, we have data coming from the repository and available to the view. All that’s left is to hook up to the view.
Step 3 – Hooking up to the real view
I’m going to cheat a bit and not define a DataTemplate for this and just directly bind to the two columns I’m going to define, Name and Number. I have a ListView in the middle of my window, as you can see in the screencapture at the top if this post. I define a couple GridViewColumns in it and bind them to Name and Number:
Obviously I wouldn’t do this on a real project, I’d use a DataTemplate. But for now, this will do. The ItemSource is set to the Players property, and the two columns are set to the fields I want to show.
We don’t have a real instance of our IPlayerRepository yet, so lets build a really simple one. In real life, this would be a repository over the top of a database, but we don’t need to go that far for now. Let’s just create the simplest repository we can for now:
One thing we’re still doing is exposing the Player class to the view. This is not necessarily the best practice that has evolved, since the Player class is defined in the Model layer. The danger is that we may end up needing to add INotifyPropertyChanged behavior to the Player class, which would pollute our domain model with view-specific code. If that were to happen, it would force us to refactor the view model to return an observable collection of something else, like an ObservableCollection<PlayerViewModel>, so that we would have a place to put our view-specific code. We don’t need to yet, so we’re not going to bother. This is a potential refactoring to come, though.
And our final change is to add the PlayerRepository type into our Application_Startup method, so that Unity knows how to build the repository and pass it into the TeamStatControlViewModel:
Compile and run, and all should work.
This was a fairly easy step to take. We exposed a property on our view model to let our view see the data we want to publish. Our view model has a repository injected into it to let it get the data as needed. That was really all there was to it. Two tests, and we got to where we needed to be.
The next step along the way will involve navigating from the front page to a player detail page. Stay tuned for the next installment, coming right up!