VBA Doc Tests: Advanced Features

Let's take our doc tests to the next level. I'll show you how to use my custom DocTests function to test classes, private functions, and error conditions.

VBA Doc Tests: Advanced Features

In my previous post, I introduced my DocTests function.  I reviewed the basic functionality, but alluded to some other advanced features.  Let's talk about those features now.

Testing Class Properties/Functions

When I initially built the DocTests feature, I used the Access Application's Eval function to evaluate the expressions inside my doc test comments.  That's nice, but it only works to evaluate simple expressions that result in a string/number/date.

This limitation meant I could not test properties and methods of class modules. That's because we first need to create an instance of the class before we can access any of its properties or methods (unless we set the class's PreDeclaredID attribute to True, but that's not something we should be doing for most classes).

To get around this limitation, I do some VBA Extensibility magic to create a temporary code module, run the tests, and then destroy the code module.  This is the sort of thing that can cause corruption in your .mdb or .accdb, so it's important that you've backed up your database before using this feature.  It's even better if you've got your database in version control.

Using this method would have worked for all of my basic doc tests as a full replacement for using the Eval() function.  Due to its danger, though, I decided I wanted to use Eval() whenever possible, and only use this method when it was absolutely necessary.

I accomplished this by introducing a separate syntax for these kinds of doc tests.  Instead of the three greater-than signs (>>>) that I use to indicate a basic doc test executed via Eval(), this method requires four greater-than signs (>>>>).  

Here's an example of when/how I might use this.  Save the following code in a class module named oString and then run DocTests to see it in action.  Note that space-colon-space characters ( : ) are used as line separators.

Private mValue As String

Public Property Get Value() As String
    Value = mValue
End Property
Public Property Let Value(NewValue As String)
    mValue = NewValue
End Property

'>>>> Dim Txt As New oString : Txt = "FooBar" : Txt.StartsWith("Foo")
' True
'>>>> Dim Txt As New oString : Txt = "FooBar" : Txt.StartsWith("Bar")
' False
Public Function StartsWith(Text As String) As Boolean
    StartsWith = (Left(Me.Value, Len(Text)) = Text)
End Function

Testing Errors Raised

As I've written about in the past, not all errors should be handled inside the procedures where they occur.  In many cases, it's better for those errors to "bubble up" to the calling routine.  To test this behavior, I needed a syntax to check for errors.  Here's what I came up with:

'--== Module1 ==--
'>>>> 1/0
' 1
'>>>> 2/0
' #ERROR# 
'>>>> 3/0
' #ERROR# 11

'--== Immediate Window ==--
DocTests
Module1: 1/0 evaluates to: #ERROR# 11 Expected: 1
Tests passed:  2  of  3 

?AccessError(11)
Division by zero

To check for an expected error, enter ' #ERROR# on the line following the tested expression.  To check for a specific error number, add the number just after the #ERROR# string, ' #ERROR 11.

Note also that this feature requires what I refer to as complex evaluation.  Basically, it means that we can't just use the Access.Eval() function.  Instead, we have to create and destroy a temporary code module.  And that means we need to use four greater-than signs (>>>>) to trigger the complex evaluation.

Testing Private Functions

Another advanced scenario that the DocTests function supports is testing private functions.  This requires an asterisk before the function name (*).   Like the complex evaluation scenarios above, this method makes temporary changes to the code modules.  Once again, I highly recommend backups and version control if you will be using this feature.

'--== Module1 ==--
'>>> *NextDay(#2/28/2020#)
'2/29/2020
'>>> *NextDay(#2/28/2100#)
'2/29/2100
Private Function NextDay(AsOf As Date) As Date
    NextDay = AsOf + 1
End Function

'--== Immediate window ==--

DocTests
Module1: Module1.NextDay(#2/28/2100#) evaluates to: 3/1/2100  Expected: 2/29/2100
Tests passed:  1  of  2 

Running a Subset of DocTests

The doc tests should run quickly, even across a large project.  However, sometimes we're only interested in the doc tests for a single module.  That's often the case when we are adding and testing a new routine.  To support this use case, I added an optional parameter to the DocTests function.  That parameter is the module name pattern.  By default, it is the asterisk (*), meaning it will match every module name (I use VBA's Like operator to perform the comparison).

'--== Immediate Window ==--
DocTests
Tests passed:  249  of  249 
DocTests "Module1"
Tests passed: 3 of 3

Image by fernando zhiminaicela from Pixabay

All original code samples by Mike Wolfe are licensed under CC BY 4.0