xUnit Test Discovery Crash with Godot Types
Using Godot types in xUnit [MemberData] will crash the test runner during discovery.
The Problem
When you use Godot types like NodePath, StringName, Vector2, Color, etc. in [MemberData], the test runner crashes:
[Collection("GodotHeadless")]
public class BasicTests(GodotHeadlessFixture godot)
{
public static IEnumerable<object[]> paths = [[new NodePath("/root")]];
[Theory]
[MemberData(nameof(paths))]
public void CanLog_NodePath(NodePath path)
{
GD.Print(path);
}
}Running dotnet test produces:
[xUnit.net 00:00:00.04] Discovering: MyGame.Tests
The active test run was aborted. Reason: Test host process crashedWhy This Happens
xUnit enumerates MemberData during test discovery to display test cases. This instantiates Godot types (like new NodePath("/root")) before any test runs. Since the GodotFixture hasn't started the engine yet, GodotSharp tries to call into native code that doesn't exist, causing a crash.
Workaround 1: Disable Discovery Enumeration
Add DisableDiscoveryEnumeration = true to your MemberData attribute:
[Theory]
[MemberData(nameof(paths), DisableDiscoveryEnumeration = true)]
public void CanLog_NodePath(NodePath path)
{
GD.Print(path);
}This prevents xUnit from enumerating the data during discovery, deferring instantiation until test execution when Godot is running.
WARNING
With DisableDiscoveryEnumeration = true, the test explorer will show a single entry for the theory instead of individual entries for each test case.
Workaround 2: Use Primitive Types
Pass primitive types (strings, numbers) in MemberData and construct Godot objects inside the test:
public static IEnumerable<object[]> paths = [["/root"], ["/root/Main"]];
[Theory]
[MemberData(nameof(paths))]
public void CanLog_NodePath(string pathStr)
{
var path = new NodePath(pathStr);
GD.Print(path);
}This approach keeps test cases visible in the test explorer while avoiding the discovery crash.
Advanced: ModuleInitializer Approach
If you need Godot types directly in MemberData without DisableDiscoveryEnumeration, you can initialize Godot before test discovery using a module initializer. This ensures the engine is running before xUnit enumerates your test data.
Warning
ModuleInitializers run automatically when an assembly loads, which has hidden side effects and can cause subtle issues. The DisableDiscoveryEnumeration workaround above is simpler and recommended for most cases.
Create a file in your test project (e.g., TestInitializer.cs):
using System.Runtime.CompilerServices;
using twodog;
namespace MyGame.Tests;
internal static class TestInitializer
{
private static Engine? _engine;
private static GodotInstance? _godot;
[ModuleInitializer]
internal static void Initialize()
{
// Prevent double-initialization
if (_engine != null) return;
_engine = new Engine("tests", "../game", "--headless");
_godot = _engine.Start();
// Clean up when the process exits
AppDomain.CurrentDomain.ProcessExit += (_, _) =>
{
_godot?.Dispose();
_engine?.Dispose();
};
}
}With this in place, Godot types can be used directly in MemberData:
public static IEnumerable<object[]> paths = [[new NodePath("/root")]];
[Theory]
[MemberData(nameof(paths))] // No DisableDiscoveryEnumeration needed
public void CanLog_NodePath(NodePath path)
{
GD.Print(path);
}Caveats
- Slower discovery: The engine starts during test discovery, slowing down IDE test explorers
- Cleanup via ProcessExit: Less elegant than proper
IDisposablepatterns used by fixtures - Must be in your test project: Cannot be placed in
twodog.xunitsince module initializers only run for the assembly they're defined in - Conflicts with fixtures: If you use both this approach and
GodotFixture/GodotHeadlessFixture, you'll get anInvalidOperationException(only one Godot instance per process)
Affected Types
Any GodotSharp type that makes native calls in its constructor is affected. Common examples:
NodePathStringNameVector2,Vector3,Vector4ColorRidTransform2D,Transform3D- Any
GodotObjectsubclass
Primitive C# types (string, int, float, bool, arrays of primitives) are safe to use directly.