gdextest is public: test your GDExtensions like you mean it
gdextesttestingrelease
Today’s short announcement: gdextest — the GDExtension Test Framework — is now public. Repository, full documentation, the works. Start here: Getting started.
The problem it solves
Testing a GDExtension used to mean one of two things. Either you built an entire test game — scenes as test cases, a script that prints PASS/FAIL into the output panel, you squinting at a wall of text — or you poked at your code through a debugger and called it testing. Neither scales, neither runs in CI, and neither tells you anything when you come back tomorrow.
gdextest flips it: you write googletest-style tests in C++, and they run inside a real, headless Godot process. Not mocks, not stubs — the actual engine, booting your code.
How it works
The framework and your suites compile together into one test-only shared object. A generated fixture project loads it, Godot 4.5 runs headless, the summary prints, the process exits 0 or 1. One command does the whole dance:
git submodule add <repo-url> extern/gdextest
git submodule update --init
// tests/smoke.cpp
#include "gdextest/assert.h"
#include "gdextest/registry.h"
GDEX_TEST(smoke, framework_is_wired) {
GDEX_EXPECT(true);
}
./extern/gdextest/gdextest test
That’s a green run. No scene plumbing, no test game, no eyeballing.
What you get
- Familiar assertions.
GDEX_EXPECT_EQ,GDEX_ASSERT_NOT_NULL,GDEX_EXPECT_STR_CONTAINS,GDEX_EXPECT_THROW— expectations record and continue, assertions stop the test. If you’ve written C++ tests before, there is nothing to learn. - Real engine access. Pure-logic tests need no engine. Tag a test
TAG_INTEGRATIONand you reach singletons,ClassDBand the live scene tree — create real nodes, parent them, free them. - Async tests. C++20 coroutines that suspend across engine frames:
co_await ctx.await_frames(2). Tweens settle, timers fire, deferred calls land — and every wait has a timeout, so a stuck test fails instead of hanging CI. - Signal monitoring.
ctx.signals()watches any object’s signals, counts emissions and records the arguments of every emission. Assert that the signal fired, fired once, and fired with the right payload. - Leak detection built in.
ctx.track_object(node)andctx.track_ref(ref)fail the test if the object is still alive at teardown. LIFO teardowns run even when the test aborts or skips. - Zero release footprint. The framework only compiles into the test build. Your shipped extension never sees it.
CI from minute one
gdextest init --ci writes a GitHub Actions workflow for you. Output comes as JSON or JUnit XML, runs can be sharded across machines and merged back with gdextest report — which itself exits 1 on any failure, so the merge step is also a gate. The doctor preflight checks config, Godot, SCons and godot-cpp before anything runs, so a broken environment fails loudly instead of mysteriously.
Where it came from
gdextest wasn’t built to be announced — it was built because Godot Code Studio needed a way to test itself, and the test-game approach made that miserable. The framework spent months quietly hardening against the studio’s own projects before it earned a public repo.
If you ship GDExtensions, give it a weekend. The full documentation covers everything from your first suite to sharded CI — and if something fights you, that’s what the issue tracker is for.
Back to the GCS trenches next.
— 5cump1 🦐