Spy
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 contrast to a mock, a spy calls the real implementation. It behaves in the same way as the normal instance.
How to use a Spy
According to the Godot core developers, overwriting core functions is no longer supported, so there is no way to spy on core functions anymore.
To spy on an object, simply use spy(<instance>). A spied instance is marked for auto-freeing, so 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 function calls and their arguments. Use the verify() method on the spy to verify that certain behavior happened at least once or an exact number of times. This way, you can check if a particular function was called and how many times it was called.
Function | Description |
---|---|
verify | Verifies that certain behavior happened at least once or an exact number of times. |
verify_no_interactions | Verifies that no interactions happened on the spy. |
verify_no_more_interactions | Verifies that the given spy has no unverified interactions. |
reset | Resets the saved function call counters on a spy. |
-
verify
The verify() method is used to verify that a function was called a certain number of times. It takes two arguments: the spy instance and the expected number of times the function should have been called. You can also use argument matchers to verify that specific arguments were passed to the function.verify(<spy>, <times>).function(<args>)
Here’s an example:
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)` to be called 3 times but it was only called 2 times verify(spyed_node, 3).set_process(true)
-
verify_no_interactions
The verify_no_interactions() method verifies that no function calls were made on the spy.verify_no_interactions(<spy>)
Here’s an example:
var spyed_node := spy(Node.new()) as Node # Test that we have no initial 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 by calling `get_name` verify_no_interactions(spyed_node)
-
verify_no_more_interactions
The verify_no_more_interactions() method verifies that all interactions on the spy have been verified. If the spy has recorded more interactions than you verified with verify(), an error is reported.verify_no_more_interactions(<spy>)
Here’s an example:
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 that the spy interacts as expected verify(spyed_node).is_a_parent_of(null) verify(spyed_node).set_process(false) # Check that there are no further interactions with the spy verify_no_more_interactions(spyed_node) # Simulate an unexpected interaction with `set_process` spyed_node.set_process(false) # Verify that there are no further interactions with the spy # and that the previous unexpected interaction is detected (the test will fail here) verify_no_more_interactions(spyed_node)
-
reset
Resets the recorded function interactions of the given spy.
Sometimes we want to reuse an already created spy for different test scenarios and have to reset the recorded interactions.reset(<spy>)
Here’s an example:
var spyed_node :Node = spy(Node.new()) # First, we test by interacting with two functions spyed_node.is_a_parent_of(null) spyed_node.set_process(false) # Verify if the interactions were recorded; 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 different scenario and we need to reset the current recorded interactions reset(spyed_node) # Verify that 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
Argument matchers allow you to simplify the verification of function calls by verifying function arguments based on their type or class. This is particularly useful when working with spys because you can use argument matchers to verify function calls without specifying the exact argument values.
For example, instead of verifying that a function was called with a specific boolean argument value, you can use the any_bool() argument matcher to verify that the function was called with any boolean value. Here’s an example:
var spyed_node :Node = spy(Node.new())
# Call the function with different arguments
spyed_node.set_process(false) # Called 1 time
spyed_node.set_process(true) # Called 1 time
spyed_node.set_process(true) # Called 2 times
# Verify that the function was called with any boolean value 3 times
verify(spyed_node, 3).set_process(any_bool())
For more details on how to use argument matchers, please see the Argument Matchers section.