In this post of the series we will build the About Dialog.
The Goal
Display a dialog showing the application name, version, link to blog and OK button.
The Model
I’m quickly learning that there is an effective order to follow when develop a new form for an MVP application:
- Model
- Presenter
- View
It is best to write the model first because both the Presenter and the View rely on the model. In particular the Model should be written and compiled before the view so you can use Data Sources in the designer to drag and drop the Model on to the form.
The AboutViewModel has just three readonly properties: ApplicationName, ApplicationVersion, BlogTitle & BlogURL.
Refactoring
Obviously the AboutViewModel shares data with the AppWindowViewModel. This shows the benefit of creating ViewModels, we can now refactor the common properties into Models and have each ViewModel refer to the model and our Views are unaffected.
Public Class AboutViewModel
Private mApplication As New ApplicationModel
Private mBlog As New BlogModel
Public ReadOnly Property Application() As ApplicationModel
Get
Return mApplication
End Get
End Property
Public ReadOnly Property Blog() As BlogModel
Get
Return mBlog
End Get
End Property
End Class
Public Class AppWindowViewModel
Public mApplication As New ApplicationModel
Public mBlog As New BlogModel
Public ReadOnly Property Name() As String
Get
Return mApplication.Name
End Get
End Property
Public ReadOnly Property BlogTitle() As String
Get
Return mBlog.Name
End Get
End Property
Public ReadOnly Property BlogURL() As String
Get
Return mBlog.URL
End Get
End Property
End Class
Namespace Models
Public Class Application
Public ReadOnly Property Name() As String
Get
Return "Building a MVP Application"
End Get
End Property
Public ReadOnly Property Version() As String
Get
Return "0.0.3.0"
End Get
End Property
End Class
End Namespace
Namespace Models
Public Class Blog
Private mApplication As New Application
Public ReadOnly Property Name() As String
Get
Return mApplication.Name
End Get
End Property
Public ReadOnly Property URL() As String
Get
Return "http://www.26tp.com/post/Building-a-MVP-framework-Table-of-Contents.aspx"
End Get
End Property
End Class
End Namespace
The Presenter
The AboutPresenter has the same requirements as AppWindowPresenter we created in Part 2 so we refactor to create the IPresenter, IView & IModel interfaces.
Public Interface IPresenter(Of TModel As IModel, TView As IView)
ReadOnly Property View() As TView
ReadOnly Property Model() As TModel
End Interface
Public Interface IModel
End Interface
Public Interface IView
Sub BindData()
End Interface
I’ve also written our first abstract class.
Public MustInherit Class Presenter(Of TModel As IModel, TView As IView)
Implements IPresenter(Of TModel, TView)
Private mView As TView
Private mModel As TModel
Public Sub Initialize(ByVal model As TModel, ByVal startupView As TView)
mModel = model.MustNotBeNull("model")
mView = startupView.MustNotBeNull("startupView")
mView.BindData()
End Sub
Public ReadOnly Property Model() As TModel Implements IPresenter(Of TModel, TView).Model
Get
Return mModel
End Get
End Property
Public ReadOnly Property View() As TView Implements IPresenter(Of TModel, TView).View
Get
Return mView
End Get
End Property
End Class
The new AboutPresenter is down to 7 lines of code.
Imports TwentySixTP.MVP
Public Class AboutPresenter
Inherits Presenter(Of AboutViewModel, AboutView)
Public Sub New()
Me.Initialize(New AboutViewModel, New AboutView(Me))
End Sub
End Class
The View
The AboutView was created with the Form Designer following these steps:
- Add AboutViewModel to Data Sources.
- Drag AboutViewModel from Data Sources on to form.
- Delete the generated text boxes.
- Update the binding property of each remaining label.
Now the form has been designed we need the following code behind:
Public Class AboutView
Implements TwentySixTP.MVP.IView
Private mPresenter As AboutPresenter
Public Sub New(ByVal presenter As AboutPresenter)
MyBase.New()
Me.InitializeComponent()
mPresenter = presenter
End Sub
Public Sub BindData() Implements TwentySixTP.MVP.IView.BindData
Me.AboutViewModelBindingSource.DataSource = mPresenter.Model
End Sub
Private Sub OK_Button_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles OK_Button.Click
Me.Close()
End Sub
Private Sub BlogLinkLabel_LinkClicked(ByVal sender As System.Object, ByVal e As System.Windows.Forms.LinkLabelLinkClickedEventArgs) Handles BlogLinkLabel.LinkClicked
System.Diagnostics.Process.Start(mPresenter.Model.Blog.URL)
End Sub
End Class
Showing Aboutview
It is the responsibility of the Application view, sorry presenter in this MVP world, to show the AboutView on startup.
Public Class AppWindowPresenter
Inherits Presenter(Of AppWindowViewModel, AppWindow)
Public Sub New()
Me.Initialize(New AppWindowViewModel, New AppWindow(Me))
mStartupView.ShowAboutView()
End Sub
End Class
The Unit Tests
We have a problem Houston. Actually we have two problems.
Problem 1
The AboutView is now being displayed during our tests. Not only is this visually annoying as AboutView is a dialog it stops the tests waiting for our response. What will the answer be????
Mock is the answer, of course!!! I’m relatively new to mocking so sorry if I’ve done some stupid.
<TestMethod()> Public Sub StartupForm_ReturnsAppWindow()
' Arrange
' ---------------------------------------------------------------------
Dim mock = New Moq.Mock(Of AppWindowPresenter)
mock.Setup(Function(m As AppWindowPresenter) m.ShowAboutView())
mock.CallBase = True
Dim presenter = mock.Object
' Act
' ---------------------------------------------------------------------
Dim actual = presenter.View
' Assert
' ---------------------------------------------------------------------
actual.ShouldNotBeNull()
actual.ShouldBeInstance(Of AppWindow)()
End Sub
The above “mocks” the AppWindowPresenter and in particular it stubs out the ShowAboutView method so the dialog does not get displayed nor pause our unit tests.
Problem 2
The unit test for AboutView.BindData fails.
<TestMethod()> Public Sub AboutView_BindsData()
' Arrange
' ---------------------------------------------------------------------
Dim presenter = New AboutPresenter
' Act
' ---------------------------------------------------------------------
Dim frm = presenter.View()
' Assert
' ---------------------------------------------------------------------
frm.BlogLinkLabel.Text.ShouldEqual(presenter.Model.Blog.Name)
End Sub
The test fails on the last line. Binding has been setup but .NET has not changed the value of frm.BlogLinkLabel.Text yet, seems not to do it until the form is actual displayed.
After some research it seems the only way for the test to work as is to show the form in the unit test. This works but I’m not sure it’s the best way to go about it. On reflection what is it we are trying to test. Can we trust that Microsoft have tested their binding framework? I think so. What we actually want to test is have we set the binding up correctly. Well of course we have if we used the designer’s drag and drop. But what if we change a property name? Because the designer uses “magic strings” it won’t recognise the refactoring. So what we need to do is test the definition of the bindings is correct. I’ll write a blog dedicated to this problem and its solution.
Source Code
Source code is available for this post via SVN:
svn export https://26tp.svn.codeplex.com/svn/tags/Building a MVP Framework - Part 3
Or to get the latest copy of the complete series code:
svn export https://26tp.svn.codeplex.com/svn/trunk/
View the 26tp.Examples.MVP project via the 26tp.Examples.sln solution.
Summary
In this post we did some refactoring to create our first interfaces and abstract class. We also introduced some mocking. Finally we found a problem unit testing data binding.
The next post, Create SQLite database from a SQL file, will look at a presenter controlling a collection of forms.