VBA-tools / vba-test

Add testing and TDD to VBA on Windows and Mac
MIT License
202 stars 46 forks source link

spy/mocks? #10

Closed rich-glow closed 9 years ago

rich-glow commented 9 years ago

Is this on the roadmap? If not do you know a way to manually mock out a function to check if it returns or to force a return value, etc?

timhall commented 9 years ago

I would love this! But, as far as I can tell, it's just not possible with VBA (I'd love to be proven wrong).

I think your best bet for something like this is using "Dependency Injection" and/or Interfaces. You can setup your dependencies as Object in your Class or Module and have them passed in by Workbook at runtime and pass in mocks for testing. You can use interfaces to avoid generic using Objects.

Example (haven't tested this, but something like it should work):

' Module.bas
' ---
Public Http As IWinHttpRequest

Public Function GetJson(Url As String) As String
    Http.Open "GET", Url
    Http.Send

    GetJson = Http.ResponseText
End Function
' Workbook
' ---
Private Sub Workbook_Open
    ' Inject runtime dependencies
    Set Module.Http = New WinHttpRequestWrapper
End Sub
' Specs_Module.bas
' ---
Public Function Specs As SpecSuite
    Set Specs = New SpecSuite
    With Specs.It("GetJson should call Open and Send")
        ' Initialize and inject test dependency
        Dim Spy As New SpyWinHttpRequest
        Set Spy.Actual = Module.Http
        Set Module.Http = Spy

        Module.GetJson "http://..."

        .Expect(Spy.OpenCallCount).ToEqual 1
        .Expect(Spy.SendCallCount).ToEqual 1

        ' Reset
        Set Module.Http = Spy.Actual
        Set Spy = Nothing
    End With
End Function
' IWinHttpRequest.cls
' ---
Public ResponseText As String

Public Sub Open(Method As String, Url As String, _
    Optional Async As Boolean = False)
End Sub

Public Sub Send(Optional Body As String = "")
End Sub
' WinHttpRequestWrapper.cls
' ---
Implements IWinHttpRequest
Private pHttp As WinHttpRequest
Private Sub IWinHttpRequest_Open(Method As String, Url As String, _
    Optional Async As Boolean = False)

    pHttp.Open Method, Url, Async
End Sub

Private Sub IWinHttpRequest_Send(Optional Body As String = "")
    pHttp.Send Body
End Sub

' ... Get/Let ResponseText

Private Sub Class_Initialize()
    Set pHttp = New WinHttpRequest
End Sub
' MockWinHttpRequest.cls
' ---
Implements IWinHttpRequest

Private Sub IWinHttpRequest_Open(Method As String, Url As String, _
    Optional Async As Boolean = False)

    ' Mock...
End Sub

Private Sub IWinHttpRequest_Send(Optional Body As String = "")
    ' Mock...
End Sub

' ... Get/Let ResponseText
' SpyWinHttpRequest.cls
' ---
Implements IWinHttpRequest
Public Actual As IWinHttpRequest
Public OpenCallCount As Long
Public SendCallCount As Long

Private Sub IWinHttpRequest_Open(Method As String, Url As String, _
    Optional Async As Boolean = False)

    OpenCallCount = OpenCallCount + 1
    Actual.Open Method, Url, Async
End Sub

Private Sub IWinHttpRequest_Send(Optional Body As String = "")
    SendCallCount = SendCallCount + 1
    Actual.Send Body
End Sub

' ... Get/Let ResponseText
rich-glow commented 9 years ago

wow thanks. I will close this as it looks outside of scope of excel-tdd

robodude666 commented 7 years ago

This is a two year old issue, but I wanted to comment in case anyone else was looking at mocking in VBA/VB6:

There is a project called VBMock which helps with creating mock classes, and a helper project called Mock Objector Generator for VBMock to help automate the generation of VBMock objects. They're both VB6-targeted, but can be used in VBA.

Rubberduck-VBA recently added a Fakes system using EasyHook to overwrite the function (within a DLL) being called at a specific ProcAddress. Both of those projects are .NET-based, but the general process applies. It's a little bit more dangerous to do directly from VBA/VB6, but should be possible.

Unfortunately, I think both of the above solutions may be out-of-scope for VBA-TDD.

The safest solution is what @timhall provided an example of: Use Interfaces and pass in Fake objects that monitor usage. It's unfortunately a very manual process but better than nothing.

-robodude666