Introduction
One of the advantages of an event sourcing (or event stream) based system is that you can very easily put together a unit test for your projection logic that uses all the same classes that will be used in the production application without any mocks at all.
So long as your event classes and your projection classes are written with no dependency on where the event stream is stored (or received from), then you can put together actual instances of the event and feed them into the projection as part of your testing.
For example, I have a system where a User can receive an "Applause
" event which adds to their Reputation cumulatively. The event to record this occurring might look like:
Public NotInheritable Class ApplaudedEvent
Inherits EventBase
Implements IEvent(Of AggregateIdentifiers.UserAggregateIdentity)
<EventVersion(1)>
Public Property Points As Integer
<EventVersion(1)>
Public Property Message As String
<EventVersion(1)>
Public Property SendBy As String
Public Overrides Function ToString() As String
Return "Applause received - " & Points.ToString() _
& " " & Message & " from " & SendBy
End Function
End Class
This event (and others) can then be fed into a User Summary projection that gives a view of the user as at a given point in time. This works by just applying the effect of each event in turn to the "current state". It might look something like:
Public NotInheritable Class UserSummaryProjection
Inherits ProjectionBase(Of AggregateIdentifiers.UserAggregateIdentity)
ReadOnly m_identity As AggregateIdentifiers.UserAggregateIdentity
Public Overrides ReadOnly Property Identity As AggregateIdentifiers.UserAggregateIdentity
Get
Return m_identity
End Get
End Property
Public Overrides Sub ConsumeEvent(ByVal sequence As Integer, _
ByVal eventToConsume As IEvent(Of AggregateIdentifiers.UserAggregateIdentity))
If IsPriorEvent(sequence) Then
Return
End If
If (TypeOf (eventToConsume) Is Events.User.CreatedEvent) Then
Dim userCreated As Events.User.CreatedEvent = eventToConsume
m_userIdentifier = userCreated.UserIdentifier
m_emailAddress = userCreated.EmailAddress
End If
If (TypeOf (eventToConsume) Is Events.User.AccountEnabledEvent) Then
m_enabled = True
End If
If (TypeOf (eventToConsume) Is Events.User.AccountDisabledEvent) Then
m_enabled = False
End If
If (TypeOf (eventToConsume) Is Events.User.ApplaudedEvent) Then
Dim userApplauded As Events.User.ApplaudedEvent = eventToConsume
m_reputationPoints += userApplauded.Points
End If
SetSequence(sequence)
End Sub
Private m_userIdentifier As String
Public ReadOnly Property UserIdentifier As String
Get
Return m_userIdentifier
End Get
End Property
Private m_emailAddress As String
Public ReadOnly Property EmailAddress As String
Get
Return m_emailAddress
End Get
End Property
Private m_enabled As Boolean
Public ReadOnly Property Enabled As Boolean
Get
Return m_enabled
End Get
End Property
Private m_reputationPoints As Integer
Public ReadOnly Property ReputationPoints As Integer
Get
Return m_reputationPoints
End Get
End Property
Public Sub New(ByVal aggregateidentity As AggregateIdentifiers.UserAggregateIdentity)
m_identity = aggregateidentity
End Sub
End Class
This projection has no run-time dependencies outside of the class itself and the events it handles, and this can be unit tested with no mocking or fakes classes something like:
<TestMethod()>
Public Sub ReputationTestMethod()
Dim expected As Integer = 3
Dim actual As Integer = 1
Dim testObj As New Projections.UserSummaryProjection_
(New AggregateIdentifiers.UserAggregateIdentity("test"))
testObj.ConsumeEvent(1, New [Event].Events.User.ApplaudedEvent()
With {.Points = 1, .Message = "Good job man"})
testObj.ConsumeEvent(2, New [Event].Events.User.ApplaudedEvent()
With {.Points = 2, .Message = "Agreed", .SendBy = "Duncan"})
testObj.ConsumeEvent(0, New [Event].Events.User.ApplaudedEvent()
With {.Points = 2, .Message = "This is out of sequence", .SendBy = "Duncan"})
actual = testObj.ReputationPoints
Assert.AreEqual(expected, actual)
End Sub
This means that you really can have a great deal of confidence in your code as it has all been unit tested.