🚀 GdUnit4 v5.0.0 - Major Architecture Overhaul
Breaking Changes ⚠️
Test Engine Redesign: GdUnit4 v5.0.0 introduces a completely reworked test engine that no longer requires the Godot runtime by default. This significantly improves test performance and enables testing in CI/CD environments without Godot installations.
Migration Required: Existing tests that depend on Godot runtime features (scenes, nodes, resources, etc.) must now be explicitly marked with the [RequireGodotRuntime]
attribute.
New Features ✨
🎯 Selective Godot Runtime
[RequireGodotRuntime]
: Annotate classes or methods that need Godot runtime context- Tests without this attribute run in fast, lightweight mode
- Dramatically improves test execution speed for logic-only tests
📊 Enhanced Debugging & Output
<CaptureStdOut>true</CaptureStdOut>
: Capture console output and Godot prints in test results[GodotExceptionMonitor]
: Monitor and report exceptions from Godot's main thread:- Catches exceptions in node callbacks (
_Ready
,_Process
, etc.) - Monitors scene tree operations
- Reports "silent" Godot exceptions as test failures
- Catches exceptions in node callbacks (
[ThrowsException]
: Comprehensive exception testing with precise validation- Better error reporting and diagnostics
🔧 Advanced Test Configuration
[DataPoint(nameof(DataSource))]
: Create data-driven tests with support for:- Static properties and methods as data sources
- Parameterized data methods with arguments
- Async data sources with
IAsyncEnumerable<object[]>
- External class data sources
[ThrowsException(typeof(ArgumentException), "message")]
: Verify expected exceptions with:- Exception type validation
- Message matching
- Source file and line number verification
- Support for multiple expected exception types
- Full VSTest filter support: Use standard test filtering in Visual Studio and
dotnet test
[TestCategory("CategoryA")]
: Organize tests with categories[Trait("Category", "A")]
: Add custom traits for test organization
Migration Guide 📋
Before v5.0.0:
[Test]
public void MyTest()
{
// All tests ran in Godot runtime
var node = new Node();
AddChild(node);
}
v5.0.0:
[Test]
public void MyLogicTest()
{
// Runs fast without Godot runtime
var result = Calculator.Add(1, 2);
AssertThat(result).IsEqual(3);
}
[Test]
[RequireGodotRuntime] // ← Add this for Godot-dependent tests
public void MyGodotTest()
{
var scene = new PackedScene();
var node = scene.Instantiate();
AddChild(node);
}
[Test]
[RequireGodotRuntime]
[GodotExceptionMonitor] // ← Monitor Godot exceptions
public void TestNodeCallback()
{
var node = new MyNode(); // Will catch exceptions in _Ready()
AddChild(node);
}
[Test]
[DataPoint(nameof(TestData))] // ← Data-driven tests
public void TestCalculations(int a, int b, int expected)
{
AssertThat(Calculator.Add(a, b)).IsEqual(expected);
}
[Test]
[ThrowsException(typeof(ArgumentNullException), "Value cannot be null")]
public void TestValidation()
{
Calculator.Add(null, 5); // Expects specific exception
}
// Data source for parameterized tests
public static IEnumerable<object[]> TestData => new[]
{
new object[] { 1, 2, 3 },
new object[] { 5, 7, 12 }
};
Performance Impact 🚀
- Non-Godot tests: Up to 10x faster execution
- CI/CD friendly: No Godot installation required for logic tests
- Selective runtime: Only pay the Godot overhead when needed
Key Attribute Features 🏷️
Data-Driven Testing
[Test]
[DataPoint(nameof(CalculationData))]
public void TestCalculations(int a, int b, int expected)
{
AssertThat(Calculator.Add(a, b)).IsEqual(expected);
}
// Multiple data source options
public static IEnumerable<object[]> CalculationData => new[]
{
new object[] { 1, 2, 3 },
new object[] { 5, 7, 12 }
};
// Parameterized data methods
[DataPoint(nameof(GetDynamicData), 10)]
public static IEnumerable<object[]> GetDynamicData(int multiplier) =>
Enumerable.Range(1, 3).Select(i => new object[] { i * multiplier });
// External data sources
[DataPoint(nameof(SharedTestData), typeof(TestDataProvider))]
public void TestWithExternalData(string input, bool expected) { }
Exception Testing
// Basic exception type verification
[Test]
[ThrowsException(typeof(ArgumentNullException))]
public void TestNullArgument() { }
// Message validation
[Test]
[ThrowsException(typeof(InvalidOperationException), "Operation not allowed")]
public void TestSpecificError() { }
// File and line verification
[Test]
[ThrowsException(typeof(ArgumentException), "Invalid value", "Calculator.cs", 25)]
public void TestPreciseLocation() { }
// Multiple possible exceptions
[Test]
[ThrowsException(typeof(ArgumentNullException))]
[ThrowsException(typeof(ArgumentException))]
public void TestMultipleExceptions() { }
Godot Integration
// Class-level runtime requirement
[RequireGodotRuntime]
public class NodeTests
{
[Before]
public void Setup() => Engine.PhysicsTicksPerSecond = 60;
}
// Method-level monitoring
[Test]
[RequireGodotRuntime]
[GodotExceptionMonitor]
public void TestNodeBehavior()
{
var node = new MyCustomNode();
AddChild(node); // Will catch exceptions in _Ready(), _Process(), etc.
}
.runsettings Configuration
<RunSettings>
<GdUnit4>
<CaptureStdOut>true</CaptureStdOut>
<Parameters>--verbose --headless</Parameters>
<DisplayName>FullyQualifiedName</DisplayName>
<CompileProcessTimeout>30000</CompileProcessTimeout>
</GdUnit4>
</RunSettings>
Summary
This release represents a major step forward in making GdUnit4 more efficient, flexible, and suitable for modern development workflows. The selective runtime approach allows developers to get the best of both worlds: lightning-fast tests for business logic and full Godot integration when needed.
Key Benefits:
- ⚡ Performance: Up to 10x faster for non-Godot tests
- 🧪 Data-Driven: Comprehensive parameterized testing with multiple data source options
- 🔍 Exception Testing: Precise exception validation with message and location verification
- 🎮 Godot Integration: Smart runtime detection and exception monitoring
- 🛠️ VSTest Compatible: Full integration with Visual Studio and
dotnet test
For technical support and questions, please visit our GitHub repository or documentation.
What's Changed
✨ New Features
- GD-138: Add capture test case execution stdout to the test report by @MikeSchulze in #139
- GD-144: Add AwaitInputProcessed to SceneRunner by @MikeSchulze in #145
- GD-46: Added support of DataPoint attributes, which make it possible to define parameterized tests with dynamic test data by @MikeSchulze in #147
- GD-153: Add Roslyn Analyzer to validate TestCase and DataPoint attribute combinations by @MikeSchulze in #154
- GD-156: Add an exception hook to report exceptions as test failures that are caught by Godot by @MikeSchulze in #157
- GD-160: Apply runsettings environment variables to the test execution context by @MikeSchulze in #161
- GD-163: Collect the Godot log file into test report by @MikeSchulze in #164
- GD-156: Install Godot exception handler and forward uncaught exceptions as test failure. by @MikeSchulze in #162
- GD-682: Rework on GdUnit4Net API Godot bridge by @MikeSchulze in #197
- GD-27: Add VSTest filter support with test categories and traits by @MikeSchulze in #201
- GD-240: Add
GDUNIT4NET_API_V5
Conditional Compilation Constant by @MikeSchulze in #241 - Upgrade to .NET 8/9 with C# 12 support by @MikeSchulze in #267
- GD-211: Implement missing
AwaitSignalOn
by @MikeSchulze in #282
🪲 Bug Fixes
- GD-149: Add error to the execution log when the test session timeout occurs by @MikeSchulze in #150
- GD-152: Fix test case display name for dynamic test data driven tests by @MikeSchulze in #176
- Fixes Godot exception monitoring issues by @MikeSchulze in #187
- Make
WithTimeout
public by @MikeSchulze in #189 - GD-199: TestRunner install ends with abnormal exit on large projects by @MikeSchulze in #200
- GD-203: Handle failure reporting for test stages [Before] and [After] by @MikeSchulze in #204
- GD-212: Fix vector assertion
IsEqualApprox
by @MikeSchulze in #214 - GD-284: Fix test failure causes the entire test execution if it is executed in the Godot Editor by @MikeSchulze in #285
🧹 Maintenance
- GD-140:
GdUnit4 API:
code cleanup and formatting by @MikeSchulze in #142 - GD-141: Code cleanup and fix formattings by @MikeSchulze in #146
🔄 CI/CD
- Update CI by @MikeSchulze in #158
- Revisit
workflow-cleanup
by @MikeSchulze in #185
New Contributors
- @dependabot made their first contribution in #181
- @renovate made their first contribution in #242
Full Changelog: v4.3.1...v5.0.0