So far we wrote a lot of code, in this blog post series related to MVVM in WPF with Visual Basic 2010. Of course there are a lot of things to improve before heading to the last part. This will also allow us discovering a great benefit from using MVVM, which is writing unit tests against the ViewModel. Let's start by refactoring some code.
Refactoring time!
Refactoring the code is something that is mainly about both ViewModels. OrdersViewModel is the one requiring more attention. The first thing we want to improve is how the code uses the RelayCommand class: since we also wrote a generic implementation it's a good idea to use this one instead of the non generic class. With that said let's move to the OrdersViewModel class placing the cursor in the code region that exposes properties of type ICommand. As an example let's start by the first one, then you will have to repeat the same work against all the other properties. Let's re-write them using RelayCommand(Of Order):
Public ReadOnly Property DeleteCommand() As ICommand
Get
If _cmdDeleteCommand Is Nothing Then
_cmdDeleteCommand = New RelayCommand(Of Order)(AddressOf DeleteExecute, AddressOf CanDeleteExecute)
End If
Return _cmdDeleteCommand
End Get
End Property
Obviously we must also change the signature of methods pointed via AddressOf, so that they receive a typed parameter instead of an Object. Here they are:
Private Function CanDeleteExecute(ByVal param As Order) As Boolean
If Me.CustomerOrdersView Is Nothing Then Return False
Return Me.CustomerOrdersView.CurrentPosition > -1
End Function
Private Sub DeleteExecute(ByVal param As Order)
Me.orderAccess.Delete(Me.CustomerOrdersView)
End Sub
Of course you need to perform the same action for all other methods. Next, let's move to the _customersView_CurrentChanged event handler. Here we can extract a new private method from the event handler's body so that we can better organize the code and providing a useful approach for unit tests (if you like):
Private Sub _customersView_CurrentChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles _customersView.CurrentChanged
Me.UpdateOrdersViewAfterCustomerChanged(CType(Me.CustomersView.CurrentItem, Customer))
End Sub
Private Function UpdateOrdersViewAfterCustomerChanged(ByVal currentCustomer As Customer) As Boolean
Try
Me.Orders = orderAccess.GetAllOrders(currentCustomer.CustomerID)
Me.CustomerOrdersViewSource.Source = Me.Orders
Me.CustomerOrdersView = CType(Me.CustomerOrdersViewSource.View, ListCollectionView)
Return True
Catch ex As Exception
Return False
End Try
End Function
Again with regard to unit test creation, instead of invoking the CustomerDataService.GetAllCustomers from within the constructor, such an invocation can be made from a new method inside the ViewModel that can be also tested and its result will be assigned to the appropriate object. Thus, first let's move at class level the following declaration:
Private dataAccess As ICustomerDataService
Once done this, let's write the following method which returns the result of the call:
Private Function GetAllCustomers() As IQueryable(Of Customer)
Return Me.dataAccess.GetAllCustomers
End Function
Then inside the constructor we need to replace the following code:
Dim dataAccess = GetService(Of ICustomerDataService)()
Me.orderAccess = GetService(Of IOrderDataService)()
For Each element In dataAccess.GetAllCustomers
Me._customers.Add(element)
Next
with this one taking advantage of the new method:
Me.dataAccess = GetService(Of ICustomerDataService)()
Me.orderAccess = GetService(Of IOrderDataService)()
For Each element In Me.GetAllCustomers
Me._customers.Add(element)
Next
Don't be afraid if you are missing something, in next blog post you will be able to download the full source code. About the OrderDetailsViewModel, we need to apply the usage of RelayCommand(Of Order_Detail). I leave this to you as an exercise, since they are the same steps shown at the beginning of this paragraph.
Writing and running unit tests
As you probably know, unit tests allow developers to test code blocks in a way that is abstracted from the application context. This means that a unit test checks if a code block works without running the application, in a fully separated context via specific tools from Visual Studio 2010. Writing unit tests is a best practice and the MVVM pattern provides the best support. In fact, how would you write unit tests against code that is associated to Views? Maybe you can do it, but this is in the UI context and is not correct. Now think of the GetAllCustomers method that we implemented before. It resides in the ViewModel, so it is separated from the UI but it can access data. So it is possible to test if it works via a unit tests checking if it actually loads data, without interfering with the UI. Even if this sample is very simple, it will let you understand this benefit. With that said, go to the OrdersViewModel class and right click the GetAllCustomers method, then select Create Unit Test. Now you will get the dialog represented in the following figure, where you can select additional methods to test and specify a test project name:
At this point Visual Studio 2010 generates the project test, and the first thing to do is copying the App.config file from the primary project to the test project since it stores data connection information. Next, in the OrdersViewModelTest.vb code file let's search for the GetAllCustomersTest method. Our goal is testing if GetAllCustomers actually loads data from the database, thus returning a non-null value. Because of this the test method can be re-written as follows:
<TestMethod(), _
DeploymentItem("adsMVVM_EntityFramework_Complete.exe")> _
Public Sub GetAllCustomersTest()
Dim target As OrdersViewModel_Accessor = New OrdersViewModel_Accessor() ' TODO: Initialize to an appropriate value
Dim actual As IQueryable(Of Customer)
actual = target.GetAllCustomers
Assert.IsNotNull(actual)
End Sub
Now, using the appropriate Visual Studio tooling (or simply right-clicking inside the code and then Run Tests) let's run the test. As you can see, the unit test passes:
This is because the GetAllCustomers method actually loaded data and thus returns something not null, adhering to Assert requisites. Now you should understand why having a ViewModel is also useful: you can test code, even when you don't have a UI.
End of the story
In next post we will finally complete our work, preparing Views that is the UI, seeing also some messages exchange, a lot of data-binding and how View is not "no code" but it is "UI code".
Alessandro