-
Notifications
You must be signed in to change notification settings - Fork 105
Stubbing
- The Fine Print
- Syntax
- Stubbing based off of parameter values
- Stubbing Packed Scenes
- Stubbing Method Paramter Defaults
- Stubbing Method Paramter Count
The stub
function allows you to set return values for methods for a doubled script or instance. You can stub any doubled class to return values. Stubs can be layered to address general and specific situations.
Because of how doubling works, you can only stub methods that are implemented in your script or any scripts that inherits from. You can stub most built-in methods if you use the experimental FULL doubling strategy. Check the bottom of the doubling page for more information.
var inst = double('res://script.gd').new()
stub(inst, 'some_method').to_return("I'm stubbed!")
-
stub(obj, method)
This stubs the value for a method. You must chain in a call to one of the "to" methods such asto_return(...)
. The first parameter can be a path to a script or a loaded script or an instance of a doubled object. When you pass in a path or class, then all doubles of that script will have the stub by default. You can override this by callingstub
on an instance of a doubled object.
After calling stub
you must chain one of the following "to" methods after.
-
.to_return(value)
Stubs the method to return the passed value. -
.to_call_super()
This will cause the method to punch through to the implementation in the super class so it will retain its original functionality. -
.to_do_nothing()
This is the same as calling.to_return(null)
but it is more readable and shows the intent of what you are doing.
You can optionally chain in the when_passed
clause as well.
-
.when_passed(p1, p2, p3....p10)
This can optionally be called on the result ofstub
in order to stub a value when specific parameters are passed to the method. It supports up to 10 parameters. Let me know if you need more. For example:
stub('res://script.gd', 'method').to_return(-5).when_passed('minus', 'five')
You can also alter a method's signature with
.param_defaults([])
-
.param_count(x)
See below for more information about setting parameter default values and changing parameter counts.
Here is a simple example
Given the double_this
class:
# res://scripts/double_this.gd
extends Node2D
var foo = -1
var bar = 10
func return_seven():
return 7
func return_hello(param=1):
return 'hello'
class InnerClass:
var another_foo = 100
Then, from inside your test script you can do the following to alter the return_seven
method to return 500
func test_something():
var inst = double('res://scripts/double_this.gd').new()
stub('res://scripts/double_this.gd', 'return_seven').to_return(500)
assert_eq(inst.return_seven(), 500)
The stub
method is pretty smart about what you pass it. You can pass it a path, an Object or an instance. If you pass an instance, it must be an instance of a double.
When passed an Object or a path, it will stub the value for all instances that do not have explicitly defined stubs. When you pass it an instance of a doubled class, then the stubbed return will only be set for that instance.
var DoubleThis = load('res://scripts/double_this.gd')
var Doubled = double('res://scripts/double_this.gd')
# these two are equivalent
stub('res://scripts/double_this.gd', 'return_seven').to_return(500)
stub(DoubleThis, 'return_seven').to_return(500)
# This will stub the value for the passed in instance ONLY.
# Any other instances will return 500 from the lines above.
var doubled_inst = Doubled.new()
stub(doubled_inst, 'return_seven').to_return('words')
You can stub a method to return a specific value based on what was passed in.
const DOUBLE_THIS_PATH = 'res://scripts/double_this.gd'
var Doubled = double(DOUBLE_THI_PATH)
stub(DOUBLE_THIS_PATH, 'return_hello').to_return('world').when_passed(7)
The ordering of when_passed
and to_return
does not matter.
When stubbing doubled scenes, use the path to the scene, not the path to the scene's script. If you double and stub the script used by the scene, the instance
you make from double_scene
will not return values stubbed for the script. It will only return values stubbed for the scene.
In order for a scene to be doubled, the scene's script must be able to be instantiated with new
with zero parameters passed.
Given the script res://the_script.gd
:
func return_hello():
return 'hello'
And given a scene with the path res://double_this_scene.tscn
which has its script set to res://the_script.gd
.
The following asserts will pass
func test_illustrate_stubbing_scenes():
const SCENE_PATH = 'res://double_this_scene.tscn'
const SCRIPT_PATH = 'res://the_script.gd'
var scene = double_scene(SCENE_PATH).instance()
var script = double(SCRIPT_PATH).new()
stub(SCENE_PATH, 'return_hello').to_return('world')
stub(SCRIPT_PATH, 'return_hello').to_return('goodbye')
assert_eq(scene.return_hello(), 'world')
assert_eq(script.return_hello(), 'goodbye')
Godot only provides information about default values for built in methods so Gut doesn't know what any default values are for methods you have created. Since it can't know, Gut defaults all paramters to null
. This can cause issues in specific cases (probably all invovling calling super). You can use .param_defaults
to specify default values to be used.
Here's an example where things go wrong
# res://foo.gd
var _sum = 0
func increment(inc_by=1):
_sum += inc_by
func go_up_one():
increment()
func get_sum():
return _sum
The following test will cause a Invalid operands 'int' and 'Nil'
error. This is because increment's inc_by
parameter is defaulted to null
in the double.
var Foo = load('res://foo.gd')
test_go_up_one_increments_sum_by_1():
var dbl_foo = double(Foo).new()
stub(dbl_foo, 'go_up_one').to_call_super()
dbl_foo.go_up_one()
assert_called(dbl_foo, 'increment', [1])
The fix is to add a param_defaults
stub
stub(dbl_foo, 'increment').param_defaults([1])
Changing the number of paramters must be done before double
is called
Some built-in methods have vararg
parameters. This makes the paramter list dynamic. Godot does not provide this information. This can cause errors due to a signature mismatch. Your code might be calling a method using 10 parameter values but Gut only sees two.
Let's take Node.rpc_id
for example. It has two normal paramters and then a vararg of strings as the last paramter.
Variant rpc_id(peer_id: int, method: String, ...) vararg
If this method gets called in a parital double with more than 2 paramters Godot will will throw Invalid call to function 'rpc_id' in base 'Control ()'. Expected 2 arguments.
You can use .param_count(x)
to tell Gut to give the method any number of extra paramters. You cannot make the method have less parameters. You must do this before you call double
.
func test_issue_246_rpc_id_varargs():
# must happen before double is called
stub(Node, 'rpc_id').to_do_nothing().param_count(5)
var inst = double(Node).new()
add_child_autofree(inst)
inst.rpc_id(1, 'foo', '3', '4', '5')
assert_called(inst, 'rpc_id', [1, 'foo', '3', '4', '5'])
You can also use .param_defaults
to specify extra paramters if you supply more defaults than the method has parameters.
func test_issue_246_rpc_id_varargs_with_defaults():
# must happen before double is called
stub(Node, 'rpc_id').to_do_nothing().param_defaults([null, null, 'a', 'b', 'c'])
var inst = double(Node).new()
add_child_autofree(inst)
inst.rpc_id(1, 'foo', 'z')
assert_called(inst, 'rpc_id', [1, 'foo', 'z', 'b', 'c'])
You cannot make a method have less paramters, only more.