Link Search Menu Expand Document

Spy

Spy is current only supported for GdScripts!

Definition

A Spy is used to verify a certain behavior during a test and tracks all function calls and their parameters of an instance.

var to_spy := spy(<instance>)

What is the difference between a spy and an mock?

In difference to a mock on a spy the real implementation is called. It will still behave in the same way as the normal instance.


How to use a Spy

To spy on a object you only need to use spy(<instance>). A spyed instance is marked for auto free, you don’t need to free it manually.

    var spy:= spy(auto_free(Node.new()))

Here a small example to use the spy on a instance of the class ‘TestClass’.

    class_name TestClass
    extends Node

        func message() -> String:
            return "a message"
    func test_spy():
        var instance = auto_free(TestClass.new())
        # build a spy on the instance
        var spy:= spy(instance)
        # call function `message` on the spy to track the interaction
        spy.message()
        # verify the function 'message' is called one times
        verify(spy, 1).message()

Verification of function calls

A spy keeps track of all the function calls and their arguments. Use verify() on the spy to verify that the specified conditions are met. This way you can check if a certain function is called and how often it was called.

Function Description
verify Verifies certain behavior happened at least once or exact number of times
verify_no_interactions Verifies no interactions is happen on this spy
verify_no_more_interactions Verifies the given spy has any unverified interaction
reset Resets the saved function call counters on a spy

verify_no_interactions

Verifies no interactions is happen on this spy.

    verify_no_interactions(<spy>)
    var spyed_node := spy(Node.new()) as Node
    
    # test we have initial no interactions on this spy
    verify_no_interactions(spyed_node)

    # interact by calling `get_name()`
    spyed_node.get_name()

    # now this verification will fail because we have interacted on this spy
    verify_no_interactions(spyed_node)

verify_no_more_interactions

Checks whether the specified spy has no further interaction.

If the spy has recorded more interactions than you verified with verify(), an error is reported.

    verify_no_more_interactions(<spy>)
    var spyed_node := spy(Node.new()) as Node
    
    # interact on two functions 
    spyed_node.is_a_parent_of(null)
    spyed_node.set_process(false)
    # verify if interacts
    verify(spyed_node).is_a_parent_of(null)
    verify(spyed_node).set_process(false)
    # finally we want to check no more interactions on this spy was happen
    verify_no_more_interactions(spyed_node)

    # simmulate a unexpected interaction on `set_process`
    spyed_node.set_process(false)
    # no the verify will fail because we have an interacted on `set_process(false)` where we not expected
    verify_no_more_interactions(spyed_node)

verify

Verifies certain behavior happened at least once or exact number of times

    verify(<spy>, <times>).function(<args>)
    var spyed_node :Node = spy(Node.new())
    
    # verify we have no interactions currently on this instance
    verify_no_interactions(spyed_node)
    
    # call with different arguments
    spyed_node.set_process(false) # 1 times
    spyed_node.set_process(true) # 1 times
    spyed_node.set_process(true) # 2 times
    
    # verify how often we called the function with different argument 
    verify(spyed_node, 1).set_process(false)# in sum one time with false
    verify(spyed_node, 2).set_process(true) # in sum two times with true

    # verify will fail because we expect the function `set_process(true)` is called 3 times but was called 2 times
    verify(spyed_node, 3).set_process(true)

reset

Resets the recorded function interactions of given spy.

Sometimes we want to reuse an already created spy for different test scenarios and have to reset the recorded interactions.

    reset(<spy>)
    var spyed_node :Node = spy(Node.new())
    
    # first testing interact on two functions 
    spyed_node.is_a_parent_of(null)
    spyed_node.set_process(false)
    # verify if interacts,at this point two interactions are recorded
    verify(spyed_node).is_a_parent_of(null)
    verify(spyed_node).set_process(false)


    # now we want to test a other scenario and we need to reset the current recorded interactions
    reset(spyed_node)
    # we verify the previously recorded interactions have been removed
    verify_no_more_interactions(spyed_node)

    # continue testing ..
    spyed_node.set_process(true)
    verify(spyed_node).set_process(true)
    verify_no_more_interactions(spyed_node)

Argument Matchers and spys

To simplify the verification of function calls, you can use an argument matcher. This allows you to verify function calls by a specific type or class argument.

    var spyed_node :Node = spy(Node.new())
    
    # call with different arguments
    spyed_node.set_process(false) # 1 times
    spyed_node.set_process(true) # 1 times
    spyed_node.set_process(true) # 2 times
    
    # verify how often we called the function with a boolean argument
    verify(spyed_node, 3).set_process(any_bool())

For more details please show at Argument Matchers


Copyright © 2021-2022 Mike Schulze. Distributed by an MIT license.