Testland
Browse all skills & agents

godot-gut-tests

Author and run GUT (Godot Unit Test) - the community-canonical GDScript test framework at github.com/bitwes/Gut and gut.readthedocs.io. Covers install (Godot Asset Library or manual `addons/gut/` copy + plugin enable), GUT panel inside the editor, writing tests that extend GutTest with `test_` prefix methods, the assertion family (assert_eq / assert_almost_eq / assert_true / assert_signal_emitted), lifecycle hooks (before_each / after_each / before_all / after_all), inner classes for grouping, parameterized tests via `params=[...]`, doubles / stubs / spies, async / coroutine tests, the command-line runner (`-d -s addons/gut/gut_cmdln.gd -gdir=res://test -gjunit_xml_file=... -gexit`), JUnit XML export, and CI integration. Godot 4.x uses GUT 9.x (current main branch supports 4.6.x; godot_4_7 branch for 4.7.x); Godot 3.x uses GUT 7.x. Use when the unit under test is GDScript code in a Godot project.

Install with skills.sh (any agent)

npx skills add testland/qa --skill godot-gut-tests
View source

godot-gut-tests

Overview

This skill wraps GUT (Godot Unit Test) (opens in new window) - the community-canonical GDScript test framework. Godot does not ship a first-party equivalent of Unity's Test Framework or Unreal's Automation System for GDScript user code.

Compatibility (per the GUT README):

EngineGUT versionBranch
Godot 4.6.x9.xmain
Godot 4.7.x9.xgodot_4_7 branch
Godot 3.4.x7.x (currently 7.4.3)maintained

Composes with:

  • platform-cert-overview-reference for the canonical six test categories GUT tests map to, and for cert-gated requirements GUT tests should cover where the title ships to Xbox / PlayStation / Switch via Godot exports.
  • gameplay-recording-replay for replay-driven coverage authored on top of GUT.

When to use

  • Unit under test is GDScript code in a Godot project (Godot 3.x or 4.x).
  • You want a before_each / after_each / parameterised / mock test surface inside the editor with a CLI runner for CI.
  • You need JUnit XML export so a generic CI dashboard (GitHub Actions test reporter, Jenkins JUnit plugin, etc.) can surface failures.

For pure-C# tests inside Godot's C# scripting layer, use .NET-canonical test runners (xUnit, NUnit) rather than GUT - GUT is GDScript-first.

Authoring

Install

Two paths:

Asset Library (recommended for compatible engine versions):

  1. Inside the Godot editor → AssetLib tab.
  2. Search for "GUT" → install.
  3. Project → Project Settings → Plugins → enable GUT.
  4. Re-launch the editor.

Manual:

  1. Clone or download the GUT repo from github.com/bitwes/Gut (opens in new window).
  2. Copy the addons/gut/ directory into your project's addons/ directory.
  3. Enable the plugin as above.

After enabling, a GUT panel appears in the bottom dock.

Project layout

Typical convention (the GUT runner defaults work with this):

my_godot_project/
  addons/gut/                # the framework
  src/
    health.gd
    enemy_ai.gd
  test/
    unit/
      test_health.gd
      test_enemy_ai.gd
    integration/
      test_save_load.gd
  .gutconfig.json            # optional config file
  project.godot

Minimal test

Per gut.readthedocs.io (opens in new window), tests "extend the GutTest class and use assertion methods like assert_eq, assert_almost_eq, assert_true, and assert_signal_emitted". Test methods are prefixed test_.

extends GutTest

func test_damage_deducts_correct_amount():
    var health := preload("res://src/health.gd").new()
    health.initialize(100)

    health.apply_damage(35)

    assert_eq(health.current, 65, "65 HP after 35 dmg from 100")

Lifecycle hooks

Lifecycle hooks:

HookScope
before_allOnce before every test in the script
before_eachBefore each test_* method
after_eachAfter each test_* method
after_allOnce after every test in the script
extends GutTest

var _player

func before_each():
    _player = preload("res://src/player.gd").new()

func after_each():
    _player.queue_free()
    _player = null

func test_player_starts_with_full_health():
    assert_eq(_player.health, _player.max_health)

Assertion family

Tests extend GutTest and call assertion methods; the most common are assert_eq / assert_ne, assert_almost_eq (floats), assert_true / assert_false, assert_null / assert_not_null, assert_signal_emitted, and assert_called (spy). The full assertion table is in references/assertions.md.

Inner classes (grouping)

Tests can be organised via inner classes:

extends GutTest

class TestAddItem:
    extends GutTest

    var _inv

    func before_each():
        _inv = preload("res://src/inventory.gd").new()

    func test_increases_count_by_stack():
        _inv.add_item("potion", 3)
        assert_eq(_inv.count_of("potion"), 3)

    func test_rejects_over_max_stack():
        var ok = _inv.add_item("potion", 999)
        assert_false(ok)

class TestRemoveItem:
    extends GutTest
    # …

Each inner class reports as its own grouping in the GUT panel.

Parameterised tests

GUT supports parameterized tests via params=[...]:

extends GutTest

var damage_cases = [
    [100, 25, 75],
    [100, 100, 0],
    [50,  60,  0],     # clamps to zero, not negative
    [100, 0,  100],
]

func test_apply_damage_table(params=use_parameters(damage_cases)):
    var health = preload("res://src/health.gd").new()
    health.initialize(params[0])
    health.apply_damage(params[1])
    assert_eq(health.current, params[2])

Each row in damage_cases becomes its own test case in the report.

Doubles, stubs, and spies

GUT provides doubling (full and partial), stubbing, and spies. Typical pattern:

extends GutTest

func test_save_calls_backend():
    var backend_double = double("res://src/backend.gd").new()
    stub(backend_double, "save").to_return(true)

    var manager = preload("res://src/save_manager.gd").new()
    manager.backend = backend_double

    manager.save_game({"hp": 50})

    assert_called(backend_double, "save", [{"hp": 50}])

double(path) produces a fake script; stub(...).to_return(value) configures return values; assert_called(...) is the spy assertion.

Async / coroutine tests

GUT supports coroutines and await in tests - a test_* method can await signals or timers and the runner waits before moving on:

extends GutTest

func test_async_load_completes():
    var loader = preload("res://src/async_loader.gd").new()
    loader.start_load("res://big_scene.tscn")

    await get_tree().create_timer(0.5).timeout

    assert_true(loader.is_done)
    assert_not_null(loader.result)

Running

From the GUT editor panel

After enabling the plugin, the GUT panel appears in the bottom dock (Editor → Bottom Panel → GUT) with normal and compact views. Click Run All or right-click a script → Run to execute.

From the command line

The command-line runner is invoked via -d -s addons/gut/gut_cmdln.gd plus GUT-specific options:

godot \
  --headless \
  -d \
  -s addons/gut/gut_cmdln.gd \
  -gdir=res://test \
  -gjunit_xml_file=artifacts/gut-junit.xml \
  -gexit

--headless runs without a display window (required for CI) and -d runs in debug mode so the runner script (addons/gut/gut_cmdln.gd) executes. The full CLI flag table and the .gutconfig.json config schema are in references/cli.md.

Parsing results

Set -gjunit_xml_file=… to export JUnit XML - the recommended CI-consumable output, in the standard schema that GitHub Actions test reporters, the Jenkins JUnit plugin, and the GitLab CI test report widget consume. GUT also tracks pre-test errors / orphan nodes / unhandled signals, which surface in the panel and the report. The full XML shape is in references/ci.md.

CI integration

Run the headless CLI command in a CI job, upload the JUnit XML as an artifact, and feed it to a JUnit-aware reporter. A complete GitHub Actions workflow (Godot download, GUT run, artifact upload, dorny/test-reporter) is in references/ci.md. For Godot 3.x projects, swap the engine version and use GUT 7.x.

Anti-patterns

Anti-patternWhy it failsFix
Forgetting --headless in CIGodot tries to open a window; CI hangs / failsAlways --headless for CI runs
Forgetting -gexitGodot stays open after the run; CI step never completesAlways -gexit for CI runs
Test scripts outside extends GutTestRunner skips them silentlyEvery test script extends GutTest (or an inner class that does)
Test methods without test_ prefixRunner skips them silentlyMethods must be prefixed test_
Using GUT 9 on Godot 3.x (or 7 on 4.x)Plugin won't loadMatch engine + GUT major-version per the compatibility table in this skill
Stubbing without a doublestub(...) requires a doubled objectUse double("res://script.gd").new() first
Async tests without awaitCoroutine completes before assertionawait the signal / timer, then assert
Asserting assert_eq on floatsFloating-point inequalityUse assert_almost_eq(a, b, tol)
Skipping JUnit XML in CICI surfaces no per-test failure detailAlways emit -gjunit_xml_file=… and feed to a CI reporter
Tests that depend on autoload singletonsCross-test contaminationRe-initialise / reset autoloads in before_each

Limitations

  • No official Godot test framework. Unlike Unity (UTF) or Unreal (Automation), Godot does not ship a first-party GDScript test framework - GUT is community-maintained at github.com/bitwes/Gut (opens in new window) (MIT license). For platform-cert evidence trails, vendor / partner reviewers may ask for the framework's provenance.
  • GDScript-only. C# Godot projects should use .NET test runners (xUnit / NUnit) - GUT is GDScript-first.
  • Godot version coupling. Bumping the engine usually bumps GUT - match the engine + GUT major version per the compatibility table in the Overview.
  • Doubles depend on script paths. double("res://path.gd") needs the script's res:// path; doubling autoloads or engine C++ classes is not supported directly.
  • No common exit-code definition for engine failures. A Godot crash mid-run gives exit 0 in some configurations - parse the JUnit XML for failures>0 as the source of truth.
  • GUT panel inside editor is the canonical UX. Running tests via --headless CI works but is slower per-test than the in-editor panel because Godot starts fresh each run.
  • gut.readthedocs.io page churn. Direct deep links (e.g., a Configuration page) sometimes return 404 between releases - the project README on github.com (opens in new window) is the most stable entry point.

GUT assertion family - reference

View source (opens in new window)

GUT assertion family - reference

Common assertions for godot-gut-tests, per gut.readthedocs.io (opens in new window) and the GUT README (opens in new window). The README notes GUT exposes "a plethora of asserts and utility methods" - check the addons/gut/test.gd source in your installed version for the complete signature list at the engine version you ship against.

AssertionUse
assert_eq(a, b, msg)Equality
assert_ne(a, b, msg)Inequality
assert_almost_eq(a, b, tol, msg)Float comparison within tolerance
assert_true(v, msg) / assert_false(v, msg)Boolean
assert_null(v, msg) / assert_not_null(v, msg)Null check
assert_has(coll, v, msg) / assert_does_not_have(coll, v, msg)Membership
assert_signal_emitted(obj, "signal_name", msg)Signal emission
assert_signal_emitted_with_parameters(obj, "name", args, msg)Signal emission with payload
assert_gt(a, b, msg) / assert_lt(a, b, msg)Ordering
assert_called(double, "method_name", args)Spy verification

Use assert_almost_eq(a, b, tol) for floats - assert_eq fails on floating-point inequality.

GUT results parsing and CI - reference

View source (opens in new window)

GUT results parsing and CI - reference

JUnit XML output and CI wiring for godot-gut-tests.

JUnit XML shape

GUT exports JUnit XML when -gjunit_xml_file=... is set - the recommended CI-consumable output per gut.readthedocs.io (opens in new window). Top-level shape:

<testsuites name="GUT" tests="42" failures="1" disabled="0" errors="0" time="3.214">
  <testsuite name="res://test/unit/test_health.gd"
             tests="5" failures="1" errors="0" time="0.124">
    <testcase classname="test_health"
              name="test_damage_deducts_correct_amount"
              time="0.012"/>
    <testcase classname="test_health"
              name="test_clamps_below_zero"
              time="0.014">
      <failure message="Expected 0 but was -5"
               type="AssertionFailed"/>
    </testcase>
  </testsuite>
</testsuites>

Standard JUnit XML - consumed by GitHub Actions test reporters, Jenkins JUnit plugin, GitLab CI test report widget, etc. Per the GUT README (opens in new window), GUT also tracks pre-test errors / orphan nodes / unhandled signals - these surface in the GUT panel and the JUnit report.

GitHub Actions

jobs:
  gut-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Install Godot
        run: |
          GODOT=4.6.1-stable
          wget -q https://github.com/godotengine/godot/releases/download/${GODOT}/Godot_v${GODOT}_linux.x86_64.zip
          unzip -q Godot_v${GODOT}_linux.x86_64.zip -d godot
          mv "godot/Godot_v${GODOT}_linux.x86_64" godot/godot
          chmod +x godot/godot
      - name: Run GUT
        run: |
          mkdir -p artifacts
          ./godot/godot --headless -d \
            -s addons/gut/gut_cmdln.gd \
            -gdir=res://test \
            -gjunit_xml_file=artifacts/gut-junit.xml \
            -gexit
      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: gut-junit
          path: artifacts/gut-junit.xml
      - uses: dorny/test-reporter@v1
        if: always()
        with:
          name: GUT
          path: artifacts/gut-junit.xml
          reporter: java-junit

For Godot 3.x projects, swap the engine version and use GUT 7.x.

GUT command-line runner - flag and config reference

View source (opens in new window)

GUT command-line runner - flag and config reference

Full flag list and config schema for godot-gut-tests. The CLI runner is invoked via -d -s addons/gut/gut_cmdln.gd plus GUT-specific options, per gut.readthedocs.io (opens in new window).

CLI flags

FlagEffect
-gdir=res://testRecurse this directory for tests
-gtest=res://test/unit/test_health.gdRun a single test script
-ginner_class=TestAddItemLimit to one inner class
-gunit_test_name=test_increases_count_by_stackLimit to one test method
-gconfig=res://.gutconfig.jsonLoad config from JSON
-gjunit_xml_file=artifacts/gut-junit.xmlWrite JUnit XML report
-gjunit_xml_timestampAdd timestamp suffix to filename
-glog=3Log verbosity (0 - 3)
-gexitExit Godot after run (essential in CI)

--headless runs Godot without a display window - required for most CI environments. -d runs in debug mode so the test runner script (addons/gut/gut_cmdln.gd) executes.

Config file

A .gutconfig.json at the project root lets the GUT panel and CLI runner share settings:

{
  "dirs": ["res://test/unit", "res://test/integration"],
  "include_subdirs": true,
  "log_level": 1,
  "junit_xml_file": "artifacts/gut-junit.xml",
  "double_strategy": "partial"
}

Field names per gut.readthedocs.io (opens in new window) - check your installed addons/gut/ version for the authoritative schema.

Related skills

game-perf-profiling

Profiles game builds against frame-time, memory, GPU draw-call, and GC-spike budgets using Unity Profiler + Profile Analyzer + Performance Testing package and Unreal Insights + stat commands. Establishes pass/fail thresholds (16.6 ms at 60 fps, 33.3 ms at 30 fps), writes automated performance regression tests that run in CI, and emits a structured budget report per SKU. Use when a title must hit a declared frame-time or memory budget before a milestone gate or platform-cert submission, or when a recent change needs a performance regression check.

gameplay-recording-replay

Build a deterministic gameplay record/replay test artefact for Unity, Unreal, or Godot - record a player session, save it to disk, replay it bit-for-bit, and assert that the resulting game state matches the original. Covers Unity Input System's InputEventTrace API (Enable / Disable / WriteTo / ReadFrom / Replay) for input-level capture, Unreal's Replay System (DemoRec / DemoPlay / DemoStop console commands plus DemoNetDriver + NetworkReplayStreamer, default storage at %LOCALAPPDATA%/{Project}/Saved/Demos) for replication-stream capture, and Godot's community-pattern deterministic-RNG + input-script replay since Godot ships no first-party replay system. Use when authoring a regression-test artefact for player-recorded sessions, building a netcode replay for spectator / esports, or producing reproducible bug repros for cert teams.

multiplayer-state-machine-coverage

Build a coverage matrix for a networked-game state machine that exercises connect / authority-handoff / disconnect / reconnect / host-migration paths across Unity Netcode for GameObjects, Unreal Engine replication, and Mirror Networking. Workflow: enumerate the engine's connection states + ownership states + replicated-property update rules, cross them against latency / loss / out-of-order packet injection, encode each combination as a test fixture, and emit a go / no-go gate. Use before submitting a multiplayer title to platform cert - Microsoft's cert guide lists 'Multiplayer does not work as expected' as one of the most common Hold reasons, and Xbox XR-067 (MPSD session state) is failed by uncovered state-machine paths.

platform-cert-overview-reference

Reference catalog of game-QA test categories and the four platform-holder certification regimes a multi-platform title submits to before release. Defines the six canonical test categories (functional / compliance / compatibility / performance / localization / accessibility) plus the multiplayer and content-rating cross-axes; documents the submission workflow, severity vocabulary, test-bench configurations, and known SLAs for Microsoft Xbox Requirements (XR), Sony TRC (gated PlayStation DevNet), Nintendo Lotcheck (gated Nintendo Developer Portal), and Steam Direct review; and includes the building-a-cert-checklist workflow that maps a target platform's requirement items to the six categories. Cites public sources inline; cites gated NDA portals by stable ID per PLUGIN_AUTHORING.md Step 4 fallback. Use when planning a game test pass or cert calendar, mapping internal QA findings to the platform's vocabulary, sequencing submissions across platforms, or emitting a pre-submission checklist.

unity-test-framework

Author and run the Unity game-engine Test Framework (`com.unity.test-framework`, currently v1.8). Distinct from the ThrowTheSwitch Unity C testing library at throwtheswitch.org/unity - the two tools share only a name. Covers package install via Package Manager, the EditMode vs PlayMode split, the [Test] / [UnityTest] / [SetUp] / [TearDown] / [UnityPlatform] attributes, assembly-definition setup (Editor folder vs asmdef with `includePlatforms` / `optionalUnityReferences: [TestAssemblies]`), Test Runner window, command-line batch invocation with `-runTests` / `-testPlatform` / `-testResults` / `-testFilter` / `-testCategory`, NUnit 3.5 assertion API, and CI integration. Use when the unit under test is C# Unity code that needs to exercise the Unity runtime or editor.

unreal-automation-system

Author and run Unreal Engine's Automation Test Framework - Epic's C++ test framework for UE 4.x / 5.x, documented at dev.epicgames.com/documentation/en-us/unreal-engine. Covers the five test categories Epic defines (Unit / Feature / Smoke / Content Stress / Screenshot Comparison), the IMPLEMENT_SIMPLE_AUTOMATION_TEST and IMPLEMENT_COMPLEX_AUTOMATION_TEST macros, the BDD-style Automation Spec API (DEFINE_SPEC / BEGIN_DEFINE_SPEC / Describe / It / BeforeEach / LatentIt / xIt), latent commands (ADD_LATENT_AUTOMATION_COMMAND), the Automation Driver for UI input simulation (IAutomationDriverModule::Get().CreateDriver(), By::Id / By::Path locators), running via Session Frontend (Window > Test Automation) and command line (-ExecCmds="Automation RunTests …"), and CI integration. Use when the unit under test is C++ Unreal code that needs the UE runtime, editor, or UMG UI surface.