Mark Gilbert's Blog

Science and technology, served light and fluffy.

Unit Testing for Events

This the second of a two-part series on Unit Testing.  The first part covers testing for exceptions, while this one will illustrate events.

Here’s the scenario that I came across a week or so ago.  I have a custom class that performs a potentially long-running import function, and I wanted to communicate to the invoking code when a record was successfully imported, and when a record was rejected for some reason (invalid data, wrong number of data points, etc.).  The invoking code was actually the user form, and it was responsible for updating record stats (“Records Imported Successfully: 120”) and logging the rejected records with a meaningful error message so the user can examine them after the fact.

Up to this point, the unit tests for the Import class would pass in a set of delimited data rows, and then analyze the actual records imported or rejected.  Now what I wanted to do was to write tests for the Imported and Rejected events that the class was going to throw.  To do that, I followed the same general idea as with the unit tests for exceptions – handle the events like I would in “real” code, and make sure that they happen at the right times (or more specifically, that they happened with the expected frequency).

First, I started with a test:

Private WithEvents _MyImport As MyBusinessServices.Import
Private _ImportedCount As Integer
Private _RejectedCount As Integer

<Test()> _
Public Sub DoImport_MinRequiredFields_AllRecordsImported()
    Me._ImportedCount = 0
    Me._RejectedCount = 0

    Me._MyImport = New MyBusinessServices.Import(42, “blah”)
    AddHandler Me._MyImport.Imported, AddressOf Me.BookImported
    AddHandler Me._MyImport.Rejected, AddressOf Me.BookRejected


    Assert.AreEqual(3, Me._ImportedCount)
    Assert.AreEqual(0, Me._RejectedCount)
End Sub

That in turn requires me to actually define the events that we’re going to be testing.  This is done as part of my Import class (the one under test):

Public Event Imported(ByVal sender As Object, ByVal e As EventArgs)
Public Event Rejected(ByVal sender As Object, ByVal e As EventArgs)

It also required me to write the event handlers (part of the test fixture):

Private Sub BookImported(ByVal sender As Object, ByVal e As EventArgs)
    Me._ImportedCount += 1
End Sub

Private Sub BookRejected(ByVal sender As Object, ByVal e As EventArgs)
    Me._RejectedCount += 1
End Sub

All I’m doing here is counting how many records were imported or rejected given a particular data set. When my test runs and the DoImport method does its thing, I expect there to be one or more events raised.  I have event handlers wired up listening for those events and they keep track of the number of times each event is raised.  The assertions at the end of the test check those values.

As you can imagine, this is only the tip of what you can do with this general idea.  In my production code, both the Imported and Rejected event use a custom class that descends from EventArgs to pass back some additional information about the item that was just imported or just rejected, and it tests that returned data to make sure it was what I expected given the input.  I’ve removed this from the above code snippets to avoid muddling the core point (testing for events).

Additionally, the code shown here lacks much of the refactoring that the test fixture eventually went through.  Again, I’ve simplified the structure to make my point clearer.

July 8, 2009 - Posted by | Agile, Visual Studio/.NET

Sorry, the comment form is closed at this time.

%d bloggers like this: