Assertions Reference
Complete API reference for all gdtest assertion functions.
All functions below are available as unqualified calls when your test class extends GdTest, or as Gdt.* static calls when it must extend an engine type. Functions written with a Gdt. prefix exist only as static calls.
For a walkthrough with context, see Writing Your First Test.
Equality
assert_eq(actual, expected, msg = "")
assert_ne(actual, not_expected, msg = "")
assert_lt(actual, bound, msg = "")
assert_lte(actual, bound, msg = "")
assert_gt(actual, bound, msg = "")
assert_gte(actual, bound, msg = "")assert_eq(calc_damage(10, 3), 7)
assert_gt(player.hp, 0)
assert_lte(elapsed_ms, 100)Float & approximate
assert_almost_eq(actual, expected, tolerance = 1e-6, msg = "")
assert_approx(actual, expected, tolerance = 1e-6, msg = "") # aliasassert_almost_eq(lerp(0.0, 1.0, 0.5), 0.5)
assert_approx(sin(PI), 0.0, 1e-5)Boolean & null
assert_true(cond, msg = "")
assert_false(cond, msg = "")
assert_null(value, msg = "")
assert_not_null(value, msg = "")assert_true(player.is_alive())
assert_null(inventory.find_item("nonexistent"))Collections
assert_has(collection, value, msg = "")
assert_not_has(collection, value, msg = "")
assert_is(obj, SomeClass, msg = "")
assert_between(value, low, high, msg = "")assert_has works on Array, Dictionary (checks keys), and String (substring check).
assert_has(inventory.items, "sword")
assert_is(spawned_node, Enemy)
assert_between(player.level, 1, 18)Strings
assert_string_contains(haystack, needle, msg = "")
assert_string_starts_with(s, prefix, msg = "")
assert_string_ends_with(s, suffix, msg = "")assert_string_contains(error_message, "out of bounds")
assert_string_starts_with(save_path, "user://")Spatial types
All use distance-based comparison:
assert_vec2_approx(a, b, tolerance = 1e-5, msg = "")
assert_vec3_approx(a, b, tolerance = 1e-5, msg = "")
assert_vec4_approx(a, b, tolerance = 1e-5, msg = "")
assert_color_approx(a, b, tolerance = 1e-5, msg = "") # max channel diff
assert_rect2_approx(a, b, tolerance = 1e-5, msg = "") # pos + sizeassert_vec2_approx(projectile.velocity.normalized(), Vector2.RIGHT)
assert_color_approx(tween_result, Color.RED, 0.01)Signals
All signal assertions must be awaited:
await assert_signal(node, "signal_name", timeout_ms = 1000)
await assert_no_signal(node, "signal_name", timeout_ms = 200)
await assert_signal_count(node, "signal_name", expected_count, timeout_ms = 1000)
await assert_signal_with_args(node, "signal_name", [arg1, arg2], timeout_ms = 1000)func test_player_emits_died() -> void:
player.hp = 0
player.take_damage(1)
await assert_signal(player, "died")assert_no_signal passes if the signal is not emitted within the timeout.
Watching and ordering signals
var watcher = watch_signal(obj, "signal_name") # records every emission
watcher.count # number of emissions so far
watcher.last_args # arguments of the latest emission
watcher.emissions # every emission's arguments
assert_emitted_before(obj_a, "sig_a", obj_b, "sig_b", msg = "")
assert_signal_order([{obj = a, sig = "started"}, {obj = b, sig = "finished"}], msg = "")Order assertions compare the first emission of each signal, which must be
watched with watch_signal before it fires. Watchers disconnect after each test.
Snapshots
assert_snapshot(actual, name = "")Serializes actual with var_to_str and compares against a stored .gdsnap file. First run creates the file. See Snapshot Testing.
Control flow
fail(msg = "") # unconditional failure
pending(msg = "") # marks test pending, continues execution
skip(msg = "") # marks test skipped, continues executionSoft assertions
Gdt.expect(cond, msg = "")
Gdt.expect_eq(actual, expected, msg = "") # also expect_ne, expect_lt, expect_lte,
Gdt.expect_true(cond, msg = "") # expect_gt, expect_gte, expect_approx,
Gdt.expect_null(value, msg = "") # expect_false, expect_not_null
assert_all(checks: Callable) -> boolEvery assertion records its failure and lets the test continue, so one run
reports every broken check. assert_all runs a block of checks and returns
false if any failed, to stop a test whose setup is broken:
if not assert_all(func() -> void:
assert_eq(player.hp, 100)
assert_eq(player.mana, 50)
):
returnAsync helpers
await wait_physics_frames(n = 1)
await wait_physics_seconds(seconds)
await wait_process_frame()
await wait_until(predicate, timeout_ms = 2000) # until predicate returns true
await wait_while(predicate, timeout_ms = 2000) # while predicate returns true
set_physics_delta(seconds) # fixed physics step for this test
reset_physics_delta()Scene helpers
add_child_autofree(node) -> Node # adds node as child; freed after test
instantiate_scene(packed) -> Node # instantiates and adds as child; freed after test
scene_runner(packed) -> SceneRunner # see SceneRunner API Reference
autoref(obj) -> Object # frees or releases obj after the testtest_world() gives a scene container that is cleaned up after the test:
var world = test_world()
var actor = world.spawn(Enemy) # class must extend Node
await world.physics_frames(10) # also process_frames(n), ready()Lifecycle hooks
func before_all() -> void # once before any test in the file
func after_all() -> void # once after all tests in the file
func before_each() -> void # before every test_*
func after_each() -> void # after every test_* (runs even on failure)Inline skip & focus
| Prefix | Behaviour |
|---|---|
xtest_ | Always skipped |
ftest_ | Only this function runs in the file |
func xtest_not_implemented_yet() -> void: pass
func ftest_the_one_i_am_debugging() -> void: ...Parameterized tests
Define a get_test_<name>_cases() function that returns an Array of [label, [args...]]:
func get_test_damage_cases() -> Array:
return [
["zero defense", [10, 0, 10]],
["partial defense", [10, 3, 7]],
["full block", [5, 10, 0]],
]
func test_damage(attack: int, defense: int, expected: int) -> void:
assert_eq(Battle.calc_damage(attack, defense), expected)Each case emits a separate result: test_damage[zero defense], test_damage[partial defense], etc.
Describe blocks
Inner classes named describe_* are treated as sub-suites. Each must declare extends GdTest:
class describe_Battle extends GdTest:
func test_basic_hit() -> void:
assert_eq(Battle.calc_damage(10, 3), 7)
func test_crit() -> void:
assert_eq(Battle.calc_damage(10, 3, true), 14)Reported as describe_Battle/test_basic_hit, describe_Battle/test_crit.
Property tests
Gdt.forall(generators: Array, check: Callable, runs = 100) -> bool
Gdt.gen_int(min, max)
Gdt.gen_float(min, max)
Gdt.gen_string(max_len = 10)
Gdt.gen_bool()
Gdt.gen_choice([a, b, c])forall calls check with one generated value per generator. On failure it
shrinks the inputs to a smaller failing case and prints it with the seed;
gdtest --seed <seed> replays it. See Running Tests.
Performance
Gdt.bench(callable, iterations = 100) -> BenchResult # mean_ms, median_ms, p95_ms, p99_ms, min_ms, max_ms
Gdt.assert_completes_in(result_or_callable, max_ms) # median under max_ms
Gdt.assert_faster_than(result_a, result_b, factor = 1.0) # a at least `factor` times faster than bErrors and warnings
Gdt.push_error(msg) # use in game code instead of push_error() to make it assertable
Gdt.push_warning(msg)
assert_push_error(text, msg = "") # text, or a regex written as "/…/"
assert_push_warning(text, msg = "")
assert_no_push_error(msg = "")Memory
assert_no_new_orphans(msg = "") # no orphan nodes since before_each
current_heap_delta() -> int # static memory change in bytes since the test startedProperties and exports
assert_exports(obj, "speed", TYPE_FLOAT, msg = "")
assert_property(obj, "speed", 300.0, msg = "") # default value on a fresh instance
assert_readonly_property(obj, "id", msg = "") # setting it has no effect
assert_property_with_range(obj, "volume", 0.0, 1.0, msg = "") # @export_range boundsFiles
assert_file(path).exists() # also is_file(), is_directory(), is_empty()
create_temp_dir(name) -> String # absolute path, created fresh
delete_temp_dir(name)Fluent assertions
Gdt.assert_that(value).is_not_null().is_equal_to(42) # any value
Gdt.assert_int(value).is_between(1, 10)
Gdt.assert_float(value).is_equal_approx(0.5, 0.01)
Gdt.assert_str(text).starts_with("user://").ends_with(".save")
Gdt.assert_array(items).contains_exactly_in_any_order(["a", "b"])
Gdt.assert_dict(data).contains_key_value("hp", 100)
Gdt.assert_object(node).is_instanceof(Enemy)
Gdt.assert_bool(flag).is_true()
await Gdt.assert_signal_of(node).is_emitted("died")Every fluent check returns the assertion, so checks chain. These mirror gdUnit4's API, see Migrating from gdUnit4.