Access JumpStart 2.0 | Blog

A Rapid Development Framework for Microsoft Access

I had a pretty “simple” change to an application in a IsLineValid function that checks the active record on a subform to see if that line is valid. A new requirement in the application meant that the conditions changed slightly.

Originally, I just added the checks directly to the code, however, I want to start doing TDD for everything. So, I’m working through the logic / work related to being able to test this “IsLineValid” function.

Initially, I was checking values right from the form itself in the test function and the test function is part of a LineController class. This means that in order to test my function, I need to instantiate a LineController class. Most of the classes functions are tightly coupled with the form, meaning they get a reference to the form to pull values directly (thisForm!Phase_Number) for example. So in order to break the dependency, I used VBA polymorphism. Here is the test which will need more modifications before it works:

RubberDuckVBA test code from my testing module:

Private Sub GivenConcreteLine_WhenNothingOrderedAndSomethingUsed_ThenLineIsValid()
    On Error GoTo TestFail
    Dim LineController As ECI_POLineController
    Set LineController = New ECI_POLineController
    Dim testDictionary As New Scripting.Dictionary
    testDictionary.Add "Phase_Number", "10"
    Dim frmGet As New FormValueGetter_Test
    Set frmGet.FieldValues = testDictionary
    Set LineController.frmGet = frmGet
    
    'thisForm causes error because it needs to return an actual form.
    'What if we changed references in IsLineValid from thisForm to a GetLineValues Object.
    'Need to figure out how to set the line values before the assert.
    Assert.AreEqual True, LineController.IsLineValid

TestExit:
    Exit Sub
TestFail:
    Assert.Fail "Test raised an error: #" & Err.Number & " - " & Err.Description
    Resume TestExit
End Sub

You can see I am creating an ECI_POLineController object which is what contains the function I’m testing.

Then I’m creating a dictionary in which I will put all the field names and values which represent a valid PO line.

Now the polymorphism begins. Here I am creating an object of type “FormValueGetter_Test” which implements FormValueGetterI which just exposes 1 method: “ReadFormValue”. Here is my FormValueGetter_Test code which allows a dictionary to be set with the proper Fieldnames and values and returns the value based on the field name:

FormValueGetter_Test:

Option Compare Database
Option Explicit

Implements FormValueGetterI

Private m_objFieldValues As Scripting.Dictionary

Private Function FormValueGetterI_ReadFormValue(FieldName As String) As Variant
    Dim retVal As Variant
    retVal = m_objFieldValues(FieldName)
    FormValueGetterI_ReadFormValue = retVal
End Function

Public Property Set FieldValues(ByVal objNewValue As Scripting.Dictionary): Set m_objFieldValues = objNewValue: End Property

Then I had to change a few things in my LineController object:

ECI_POLineController

'Added to declarations
Private m_objfrmGet As FormValueGetterI

'...lots of other code

'Added a public property so the FormValueGetter object could be set if desired.
Public Property Set frmGet(ByVal objNewValue As FormValueGetterI): Set m_objfrmGet = objNewValue: End Property

'We will use this function to get the form values
'We can set it in the test environment, and if it's not set, this will use a default object.
Private Function thisFormGet(FieldName As String) As Variant
    Dim retVal As Variant
    If m_objfrmGet Is Nothing Then Set m_objfrmGet = New FormValueGetter_orderlineitemsForm
    retVal = m_objfrmGet.ReadFormValue(FieldName)
    thisFormGet = retVal
End Function

'...lots more code

'And now the testing function with all it's sub functions.  Now all the sub functions use
'the new thisFormGet function to retrieve the values

Public Function IsLineValid() As Boolean
    IsLineValid = IsPhaseValid _
            And IsItemDescriptionValid _
            And IsQtyValid _
            And IsUnitOfMeasureValid And IsCostTypeValid And IsTaxableValid
End Function

Private Function IsPhaseValid() As Boolean
    IsPhaseValid = Not ISNULL(thisFormGet("Phase_Number")) And thisFormGet("Phase_Number") <> "" And thisFormGet("Phase_Number") <> 0
End Function

Private Function IsItemDescriptionValid() As Boolean
    IsItemDescriptionValid = Not ISNULL(thisFormGet("Item_Description")) And thisFormGet("Item_Description") <> ""
End Function

Private Function IsQtyValid() As Boolean
    Dim retVal As Boolean
    If LineControlManager.LineIsConcrete Then
        retVal = IsQtyOrderedValid Or IsQtyUsedValid
    Else
        retVal = IsQtyOrderedValid
    End If
    IsQtyValid = retVal
End Function

Private Function IsQtyOrderedValid() As Boolean
    IsQtyOrderedValid = Not ISNULL(thisFormGet("Qty_Ordered")) And thisFormGet("Qty_Ordered") <> "" And thisFormGet("Qty_Ordered") <> 0
End Function

Private Function IsQtyUsedValid() As Boolean
    IsQtyUsedValid = Not ISNULL(thisFormGet("Qty_Used")) And thisFormGet("Qty_Used") <> "" And thisFormGet("Qty_Used") <> 0
End Function

Private Function IsUnitOfMeasureValid() As Boolean
    IsUnitOfMeasureValid = Not ISNULL(thisFormGet("Unit_of_Measure")) And thisFormGet("Unit_of_Measure") <> ""
End Function

Private Function IsCostTypeValid() As Boolean
    IsCostTypeValid = ISNULL(thisFormGet("Cost_Type_ID")) = False
End Function

Private Function IsTaxableValid() As Boolean
    IsTaxableValid = ISNULL(thisFormGet("Taxable")) = False
End Function

So, after all this, I ran into another dependency. This time I am using the “LineControlManager.LineIsConcrete” function to determine if this is a concrete based line. This function uses the LineControlManager object which uses it’s own reference to the form again and so I will need to determine how best to break this second dependency. I think that will allow me to finish writing the test.