Doubles
If you aren’t sure what a Double is, check out Test Double on Wikipedia.
You create a Double by calling the double
method which in your GutTest
script. The double
method accepts a loaded script or scene and returns a class based on the loaded script or scene passed to it. The returned class/scene wraps the source and the methods defined will not execute the code defined in them. These doubles can then be stubbed and spied on in tests. You can also create Partial Doubles which retain their functionality by default.
Using your double, you can:
Stub methods to return different values.
Stub them to call the super method.
Assert how many times a method was called.
Assert a method was called with specific parameters.
And much much more. See Stubbing and Spies for more information.
The following methods cannot be spied on due to implementation details with either Gut or GDScript. There might be more.
has_method _draw
get_script _physics_process
get _input
_notification _unhandled_input
get_path _unhandled_key_input
_enter_tree _get
_exit_tree emit_signal
_process _set
Characteristics of a Double
The double inherits (
extends
) the source.Contains all class level variables defined in the source.
Contains all signals defined in the source.
Methods defined in the source (and any user defined super class):
Do nothing (unless stubbed).
Will return
null
(unless stubbed).All parameters are defaulted to
null
, even if they did not have a default value originally. You can stub parameter defaults (see Stubbing).
Methods defined in any Godot super class (such as
Node
orControl
) are not altered unless those methods have been overridden. This is overridable by changing the Double-Strategy.Inner Classes of the source are not doubled and will retain their functionality.
You can double Inner Classes, but it requires an extra step. See the Inner Class section below.
If your
_init
method has required parameters you must stub default values before trying todouble
the object.Any static methods you add to your scripts must be ignored before doubling using
ignore_method_when_doubling
. More information about this below.You can double Scripts, Inner Classes, and Packed Scenes. Once you have a double, you can then call
new
orinstantiate
on it to create instances of a doubled object.All instances of Doubles and Partial Doubles are freed when a test finishes. This means you do not have to free them manually and you should not be created in
before_all
or referenced inafter_all
.
Doubling a Script
To double a script just give it a path or an already loaded script.
var MyScript = load('res://my_script.gd')
# Load the doubled object.
var DoubledMyScript = double(MyScript)
# Create an instance of a doubled object
var doubled_script = DoubledMyScript.new()
# or
var doubled_script = double(MyScript).new()
Doubling Inner Classes
Inner Classes cannot be automatically detected and therefore must be registered with GUT before they can be doubled. You do this by calling register_inner_classes(Foo)
. You only have to do this once per script/scene that contains Inner Classes, so it is best to call it in before_all
or a pre-hook script. Registering multiple times does nothing. Failing to call register_inner_classes
will result in a GUT error and a runtime error.
# Given that SomeScript contains the class InnerClass that
# you wish to to double:
var SomeScript = load('res://some_script.gd')
func before_all():
register_inner_classes(SomeScript)
func test_foo():
var dbl = double(SomeScript.InnerClass).new()
This approach was used to make tests cleaner and less susceptible to typos. If Godot adds meta data to inner classes that point back to the source script, then register_inner_classes
can be removed later and no other changes will need to be made.
Doubling a Scene
A doubled version of your scene is created along with a double of its script. The doubled scene is altered to load the doubled script instead of the original. A reference to the newly doubled scene is returned. You can call instantiate
on the returned reference.
var MyScene = load('res://my_scene.tscn')
var DoubledScene = double(MyScene)
# Create an instance
var doubled_scene = DoubledScene.instantiate()
# or
var doubled_scene = double(MyScene).instantiate()
Doubling Scripts with Static Methods
Currently you cannot double static methods. In fact if you try to double a class with a static method then you will get an error that looks similar to:
Parser Error: Function signature doesn't match the parent. Parent signature is: 'Variant foo()'.
As of now, GUT does not have the ability to detect static methods in the code. As a workaround there is the ignore_method_when_doubling
method. This method takes in a variant as the first parameter and a method name as the second. The first parameter can be a path to a script, a path to a scene, a loaded script, or a loaded scene.
Calling this method will prevent GUT from trying to make a stubbed out version of the method in the generated double allowing you to successfully double your classes that contain static methods.
These ignored methods are cleared after each test is ran to avoid any unexpected results in your tests, so you may want to add this call to your before_each
.
There’s more info and examples on this method on the [[Methods|Asserts-and-Methods]] page.
Doubled method default parameters
GUT stubs all parameters in doubled user methods to be null
. This is because Godot only provides defaults for built-in methods. When using Partial Doubles or stubbing a method to_call_super
, null
can get passed around when you wouldn’t expect it causing errors such as Invalid operands 'int' and 'Nil'
. See the “Stubbing Method Parameter Defaults” in Stubbing for a way to stub method default values.
Methods with varargs and NativeScript parameter mismatches
Some methods provided by Godot can contain and endless list of parameters (varargs). Trying to call one of these methods on a double can result in runtime errors due to a parameter mismatch. See the sections in Stubbing that address parameters.
GUT can detect these vararg parameters and will stub the doubled method to accept 10 values which are all defaulted to null
.
For example, the signature for Node’s rpc_id
function is:
Error rpc_id ( int peer_id, StringName method, ... ) vararg
Here the ...
is the placeholder for the vararg parameter. When GUT encounters this kind of signature it will generate the following:
func rpc_id (peer_id, method, arg1=null, arg2=null, arg3=null, arg4=null, arg5=null, arg6=null, arg7=null, arg8=null, arg9=null, arg10=null)
You can change the number of arguments passed and their default by stubbing parameters.
Doubling Built-Ins
You can double
built-in objects that are not inherited by a script such as a Node2D
or a Raycast2D
. These doubles are always created using the Doubling Strategy of INCLUDE_NATIVE
(see Double-Strategy).
For example you can double
or partial_double
like this:
var doubled_node2d = double(Node2D).new()
stub(doubled_node2d, 'get_position').to_return(-1)
var partial_doubled_raycast = partial_double(Raycast2D).new()