I’ve been doing some investigation of Passive View (MVP) architecture for Smart Client applications.
The article Raising events (from a mock) using Rhino Mocks By Jean-Paul S. Boodhoo outlines the issues and gives some solutions to mocking “View” classes in MVP from a C# point of view. In this post I’ll show some equivalent VB.Net code samples.
About Passive View
The Passive View involves three components:
- Model – Responsible for reading, saving and validating the business data
- View – Responsible for displaying data and accepting input from the user
- Presenter – It provides the “glue” between the View and Presenter. It copies data from the Model to the View and relays commands from the View to the Model.
(This differs from MVC in that there is no direct communication between Model and View)
The model and the view implement interfaces which are used by the Presenter to communicate with them.
By utilising interfaces, these interfaces can be “Mocked” using a mocking framework such as Rhino Mock.
About Mocking
The purpose of mocking is to ease testing by allowing the developer to create mock implementations of custom objects and verify the interactions using unit testing.
In MVP, the view is usually implemented by a difficult-to-test (or slow-to-test) user interface form. Using a Mocking framework allows you to create a substitute for the user interface that runs quickly and accurately simulates the user interface from the Presenter’s viewpoint.
Find out more about Rhino Mocks at it’s project page.
Raising Events
An important aspect of the View interface is that it defines events that are raised in order to communicate input actions to the Presenter.
This is not completely straight forward.
Here’s an example of one of a View interface:
Public Interface IPersonalView
Property Name() As String
Property Address() As String
Property Age() As Integer
Event OnRead()
Event OnSave()
End Interface
Here’s an example of a presenter that consumes views that implement the IPersonalView interface:
Public Class PersonalPresenter
Protected WithEvents View As IPersonalView
Protected Model As IPersonalModel
Public Sub New(ByVal view As IPersonalView, ByVal model As IPersonalModel)
Me.View = view
Me.Model = model
End Sub
Public Sub DisplayCustomerDetails() Handles View.OnRead
' Ask Model to populate itself
Model.DoRead()
' Transfer information
With Me.View
.Name = Model.Name
.Address = Model.Address
.Age = Model.Age
End With
End Sub
Public Sub DoUpdate() Handles View.OnSave
With Me.Model
.Name = View.Name
.Address = View.Address
.Age = View.Age
End With
Model.DoSave()
End Sub
End Class
You’ll notice that the first thing the presenter does (using the WithEvents keyword) is subscribe to events.
In order to correctly “mock” a view then, we have to program the mock to expect this subscription. Here is a Test:
<TestClass()> _
Public Class PersonalPresenterTest
Private moMockery As MockRepository
<TestInitialize()> _
Public Sub MyTestInitialize()
moMockery = New MockRepository
End Sub
<TestCleanup()> _
Public Sub MyTestCleanup()
moMockery.VerifyAll()
End Sub
<TestMethod()> _
Public Sub Construction_ShouldSubscribeToEventsOnView()
' Mock the View
Dim loView As IPersonalView = moMockery.CreateMock(Of IPersonalView)()
AddHandler loView.OnRead, Nothing
LastCall.Constraints(Constraints.Is.NotNull())
AddHandler loView.OnSave, Nothing
LastCall.Constraints(Constraints.Is.NotNull())
moMockery.ReplayAll()
Dim target As PersonalPresenter = New PersonalPresenter(loView, Nothing)
End Sub
End Class
The important lines here are:
AddHandler loView.OnRead, Nothing
LastCall.Constraints(Constraints.Is.NotNull()) The first line simulates what the WithEvents call does, which is to call AddHandler on all the handled events.
Raising An Event
Listening for the wireup isn’t enough however. To properly simulate true view actions, we need to raise events to the Presenter (such as OnRead). Rhino Mock provides a special object called an “EventRaiser”.
Call LastCall.GetEventRaiser after the AddHandler to get this object, then call the Raise method to raise the event:
' Mock the View
Dim loView As IPersonalView = moMockery.CreateMock(Of IPersonalView)()
AddHandler loView.OnRead, Nothing
LastCall.Constraints(Constraints.Is.NotNull())
Dim loOnReadEventRaiser As Rhino.Mocks.Interfaces.IEventRaiser = LastCall.GetEventRaiser
AddHandler loView.OnSave, Nothing
LastCall.Constraints(Constraints.Is.NotNull())
moMockery.ReplayAll()
Dim target As PersonalPresenter = New PersonalPresenter(loView, loModel)
' Raise the read event on the "view"
loOnReadEventRaiser.Raise()
This gives me the fundamental building blocks for mocking views.