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.