Skip to content

xUnit Host

MyGame.tests runs tests against a real Godot engine, project, and resources. Because xUnit owns the process, an xUnit fixture owns the engine lifetime.

bash
dotnet test MyGame.tests

This page covers host anatomy

Testing with xUnit is the canonical guide to fixtures, engine arguments, collections, filtering, and CI workflows.

Test Anatomy

csharp
using Godot;
using twodog.Testing;
using twodog.Testing.Xunit;
using Xunit;

namespace MyGame.Tests;

[Collection<HeadlessCollection>]
public class BasicTests(HeadlessFixture godot)
{
    [Fact]
    public void LoadMainScene_Succeeds()
    {
        var mainScene = (string)ProjectSettings.GetSetting("application/run/main_scene", "");
        Assert.SkipWhen(mainScene == "", "No run/main_scene configured in project.godot");

        var instance = GD.Load<PackedScene>(mainScene).Instantiate();
        godot.Tree.Root.AddChild(instance);

        Assert.NotNull(instance.GetParent());
    }
}

The fixture exposes the same objects a generic host keeps in local variables:

Fixture memberConsole equivalent
godot.Enginenew Engine(...)
godot.GodotInstanceengine.Start()
godot.Treeengine.Tree

HeadlessFixture passes --headless; Fixture enables rendering. Both derive from FixtureBase.

Project Differences

The shared host project is documented in Hosts. The test host references 2dog.xunit, xUnit, the test SDK, and MyGame.csproj. 2dog.xunit brings in 2dog.engine, fixtures, and collection definitions.

xml
<ItemGroup>
  <PackageReference Include="2dog.xunit" Version="4.7.1.65"/>
  <PackageReference Include="xunit.v3" Version="3.*"/>
  <PackageReference Include="xunit.runner.visualstudio" Version="3.*"/>
  <PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.*"/>
  <PackageReference Include="coverlet.collector" Version="10.*"/>
</ItemGroup>

<ItemGroup>
  <ProjectReference Include="../MyGame.csproj"/>
</ItemGroup>

<PropertyGroup>
  <GodotProjectDir>..</GodotProjectDir>
  <TwoDogVariant Condition="'$(Configuration)' == 'Debug'">debug</TwoDogVariant>
  <TwoDogVariant Condition="'$(Configuration)' == 'Editor'">editor</TwoDogVariant>
  <TwoDogRemoveDuplicateGodotAnalyzers>true</TwoDogRemoveDuplicateGodotAnalyzers>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)' == 'Editor'">
  <DefineConstants>$(DefineConstants);EDITOR</DefineConstants>
</PropertyGroup>

<ItemGroup Condition="'$(Configuration)' == 'Editor'">
  <PackageReference Include="GodotSharpEditor" Version="4.7.1.*"/>
</ItemGroup>

GodotProjectDir enables automatic resource import, so tests see freshly imported assets. Debug, Release, and Editor configurations select the matching native variant; see Choosing a Variant.

Single-Instance Safety

A normal test host supports one active engine at a time. Tests must use a non-parallel collection, and all tests in that collection share its engine. The generated host also sets "parallelizeTestCollections": false in xunit.runner.json.

2dog.xunit provides RenderingCollection and HeadlessCollection as compile-in source because xUnit discovers collection definitions only in the test assembly. Experimental parallel engines are covered in Miscellaneous Advanced Notes.

Remember that nodes added to the shared tree are not cleaned up automatically; QueueFree() what you create. See the known issues for Godot types in MemberData and GD.Print output.