Godot 4.4.1 Testing Guide with C# and GdUnit4
Table of Contents
- External Editor Configuration
- Unit Testing Configuration with GdUnit4
- Project Structure
- Advanced Configuration
- Best Practices
- Troubleshooting
External Editor Configuration
Visual Studio Code (Recommended)
Install required extensions:
- C# Dev Kit (Microsoft)
- C# (Microsoft)
- godot-tools (optional, for .gd file syntax highlighting)
Configure in Godot:
- In
Editor → Editor settings → Dotnet → Editor
, set:- External Editor:
Visual Studio Code and VSCodium
- Exec Path: Path to your VS Code executable
- Windows:
C:\Users\[username]\AppData\Local\Programs\Microsoft VS Code\Code.exe
- Linux:
/usr/bin/code
- macOS:
/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code
- Windows:
- External Editor:
- In
Configure VS Code workspace: Create a
.vscode/settings.json
file at the project root:{ "dotnet.defaultSolution": "Ashes of Velsingrad.sln", "files.exclude": { "**/.godot/": true, "**/.import/": true } }
Visual Studio (Alternative)
- Configure in Godot:
- External Editor:
Visual Studio
- Exec Path: Path to devenv.exe
- Example:
C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\IDE\devenv.exe
- Example:
- Exec Flags:
{project} --goto {file}:{line}:{col}
- External Editor:
JetBrains Rider (Alternative)
- Configure in Godot:
- External Editor:
JetBrains Rider and Fleet
- Exec Path: Path to rider64.exe
- Exec Flags:
{project} --line {line} {file}
- External Editor:
Unit Testing Configuration with GdUnit4
Environment Setup
Set the
GODOT_BIN
environment variable:This variable must point to the Godot Mono executable (for example,
Godot_v4.4.1-stable_mono_win64.exe
). It is required to run C# tests with GdUnit4. You can set this variable:System-wide (recommended): via your operating system's environment variables:
Variable name:
GODOT_BIN
Value: Full path to the Godot Mono executable
Example:
C:\Program Files\Godot\Godot_v4.4.1-stable_mono_win64.exe
Or locally for tests, by adding it to the
tests/.runsettings
file, insideRunConfiguration
:
<EnvironmentVariables> <GODOT_BIN>C:\path\to\Godot_v4.4.1-stable_mono_win64.exe</GODOT_BIN> </EnvironmentVariables>
Tip: Prefer system-wide configuration to avoid updating the configuration file each time you change machines or installation paths.
Add GdUnit4Net NuGet packages: Run these commands in your project directory:
dotnet add package gdUnit4.api --version 5.0.0 dotnet add package gdUnit4.test.adapter --version 3.0.0 dotnet add package gdUnit4.analyzers --version 1.0.0
Installation Verification
Your project structure should look like this:
AshesOfVelsingrad/
├── addons/
│ └── gdUnit4/
├── tests/
│ ├── unit/
│ │ └── TestTemp.cs
│ ├── integration/
│ └── .runsettings
└── project.godot
C# Project Testing Configuration
Verify NuGet packages in your
.csproj
file:<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" /> <PackageReference Include="gdUnit4.api" Version="5.0.0" /> <PackageReference Include="gdUnit4.test.adapter" Version="3.0.0" /> <PackageReference Include="gdUnit4.analyzers" Version="1.0.0">
Enhanced unit test example:
using AshesofVelsingrad; using GdUnit4; using Godot; using static GdUnit4.Assertions; using System; namespace Tests.Unit { [TestSuite] public class UnitTestExample { [TestCase] public void TestBasicAssertion() { AssertThat(2 + 2).IsEqual(4); } [TestCase] [RequireGodotRuntime] public void TestGodotNode() { var node = AutoFree(new Node()); AssertThat(node).IsNotNull(); AssertThat(node != null ? node.Name : throw new NullReferenceException("node is null")).IsEqual(""); node.Name = "TestNode"; AssertThat(node.Name).IsEqual("TestNode"); } [TestCase] [RequireGodotRuntime] public void TestGodotNodeWithManualCleanup() { Node? node = null; try { node = new Node(); AssertThat(node).IsNotNull(); AssertThat(node.GetType().Name).IsEqual("Node"); node.Name = "ManualTestNode"; AssertThat(node.Name).IsEqual("ManualTestNode"); } finally { node?.QueueFree(); } } [TestCase] [RequireGodotRuntime] public void TestGodotNodeWithSceneTree() { var scene = AutoFree(new Node()); var child = AutoFree(new Node()); if (scene == null) throw new NullReferenceException("scene is null"); if (child == null) throw new NullReferenceException("child is null"); scene.AddChild(child); AssertThat(scene.GetChildCount()).IsEqual(1); AssertThat(scene.GetChild(0)).IsEqual(child); } [TestCase] [RequireGodotRuntime] public void TestNodeProperties() { var node = AutoFree(new Node()); if (node == null) throw new NullReferenceException("node is null"); AssertThat(node.GetInstanceId()).IsGreater(0); AssertThat(node.IsInsideTree()).IsFalse(); node.Name = "TestNode"; AssertThat(node.Name).IsEqual("TestNode"); } [TestCase] [RequireGodotRuntime] public void TestNodeHierarchy() { var parent = AutoFree(new Node()); var child1 = AutoFree(new Node()); var child2 = AutoFree(new Node()); if (parent == null) throw new NullReferenceException("parent is null"); if (child1 == null) throw new NullReferenceException("child1 is null"); if (child2 == null) throw new NullReferenceException("child2 is null"); parent.Name = "Parent"; child1.Name = "Child1"; child2.Name = "Child2"; parent.AddChild(child1); parent.AddChild(child2); AssertThat(parent.GetChildCount()).IsEqual(2); AssertThat(child1.GetParent()).IsEqual(parent); AssertThat(child2.GetParent()).IsEqual(parent); AssertThat(parent.GetChild(0).Name).IsEqual("Child1"); AssertThat(parent.GetChild(1).Name).IsEqual("Child2"); } } }
Integration Testing Best Practices
[TestSuite] public class PlayerCombatIntegrationTests { [TestCase] [RequireGodotRuntime] public void Should_ApplyDamage_When_PlayerAttacksEnemy() { // Arrange: Set up player and enemy with components var player = AutoFree(new Player()); var enemy = AutoFree(new Enemy()); if (player == null) throw new NullReferenceException("player is null"); if (enemy == null) throw new NullReferenceException("enemy is null"); // Act: Simulate combat interaction player.Attack(enemy); // Assert: Verify the complete interaction chain AssertThat(enemy.GetComponent<HealthComponent>().CurrentHealth) .IsLess(enemy.GetComponent<HealthComponent>().MaxHealth); } }
Running Tests
From Godot (Recommended):
- Go to the "MSBuild" tab
- Rebuild the project
- Go to the "GdUnit4" tab
- Click "Run discover tests"
- Select your tests and click "Run"
From VS Code:
- Use the C# Dev Kit extension
- Open the "Test Explorer" panel
- Click "Refresh Tests"
- Click "Run Test"
From command line:
dotnet test --settings tests/.runsettings
Tip: Tests marked with the
RequireGodotRuntime
attribute can only be executed within the Godot Engine. When running tests outside of Godot, these tests will be skipped or may block execution of other tests. For best results, run allRequireGodotRuntime
tests from within the Godot Editor.
Project Structure
Organization
YourProject/
├── addons/
│ └── gdUnit4/
├── docs/
│ ├── docfx/
│ │ ├── ...
│ ├── CONTRIBUTING.md
│ └── SETUP.md
├── scripts/
│ ├── player/
│ ├── enemy/
│ ├── gui/
│ └── utils/
├── tests/
│ ├── unit/
│ │ ├── player/
│ │ ├── enemy/
│ │ ├── gui/
│ │ └── utils/
│ ├── integration/
│ └── .runsettings
├── scenes/
├── assets/
├── .editorconfig (already configured)
├── .gitignore (already configured)
└── project.godot
File Organization Principles
- Mirror test structure: Test files should mirror your main script structure
- Separate concerns: Keep unit tests and integration tests in separate folders
- Follow naming conventions: Use clear, descriptive names following the project's contributing.md guidelines
Advanced Configuration
Debugger Configuration
For VS Code, create .vscode/launch.json
:
{
"version": "0.2.0",
"configurations": [
{
"name": "Launch Godot",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
"program": "${env:GODOT_BIN}",
"args": ["--path", "${workspaceFolder}"],
"cwd": "${workspaceFolder}",
"console": "internalConsole",
"stopAtEntry": false
}
]
}
Task Configuration
Create .vscode/tasks.json
for build tasks:
{
"version": "2.0.0",
"tasks": [
{
"label": "build",
"command": "dotnet",
"type": "process",
"args": ["build"],
"group": "build",
"presentation": {
"echo": true,
"reveal": "silent",
"focus": false,
"panel": "shared"
},
"problemMatcher": "$msCompile"
},
{
"label": "test",
"command": "dotnet",
"type": "process",
"args": ["test", "--settings", "tests/.runsettings"],
"group": "test",
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "shared"
}
}
]
}
Best Practices
Unit Testing
Test naming:
- Use descriptive names:
Should_ReturnTrue_When_PlayerIsAlive
- Follow AAA pattern (Arrange, Act, Assert)
- Use descriptive names:
Organization:
- One test file per tested class
- Group tests by functionality
Mocking:
[TestCase] public void TestWithMock() { var mockNode = AutoFree(Mock(Node.class)); // Configure mock When(mockNode.GetName()).ThenReturn("MockedNode"); // Test AssertThat(mockNode.GetName()).IsEqual("MockedNode"); }
C# Code in Godot
Use Godot attributes:
[Export] public int Health { get; set; } = 100; [Signal] public delegate void HealthChangedEventHandler(int newHealth);
Resource management:
public override void _ExitTree() { // Clean up resources base._ExitTree(); }
Commit Messages
Follow the project's CONTRIBUTING.md guidelines for commit message conventions. The project uses Conventional Commits with specific scopes like player
, combat
, inventory
, ui
, audio
, level
, ai
, save
, network
, build
, and config
.
Troubleshooting
Common Issues
Tests don't run:
- Check that the
GODOT_BIN
environment variable is correctly set - Ensure GdUnit4 is enabled in the project plugins
- Verify the path points to the mono version:
Godot_v4.4.1-stable_mono_win64.exe
- Check that the
IntelliSense not working:
- Regenerate project files:
Project → Tools → C# → Sync C# Project
- Restart your editor
- Regenerate project files:
Build errors:
- Check that your .NET version is compatible (6.0+ recommended)
- Clean and rebuild:
dotnet clean && dotnet build
Useful Commands
# Clean project
dotnet clean
# Complete rebuild
dotnet build --no-restore
# Run tests with verbose output
dotnet test --logger "console;verbosity=detailed"
# Generate coverage report
dotnet test --collect:"XPlat Code Coverage"
# Add GdUnit4Net packages
dotnet add package gdUnit4.api --version 5.0.0
dotnet add package gdUnit4.test.adapter --version 3.0.0
dotnet add package gdUnit4.analyzers --version 1.0.0
Logging and Debugging
Enable detailed logs in Godot:
Project → Project Settings → Debug → Settings
- Enable "Verbose stdout"
Logs in tests:
[TestCase] public void TestWithLogging() { GD.Print("Debug message from test"); AssertThat(true).IsTrue(); }
Environment Variable Setup (Windows)
Via System Properties:
- Press
Win + R
, typesysdm.cpl
- Go to "Advanced" tab → "Environment Variables"
- Add new system variable:
- Name:
GODOT_BIN
- Value:
C:\path\to\Godot_v4.4.1-stable_mono_win64.exe
- Name:
- Press
Via Command Line:
setx GODOT_BIN "C:\path\to\Godot_v4.4.1-stable_mono_win64.exe"
Via PowerShell:
[Environment]::SetEnvironmentVariable("GODOT_BIN", "C:\path\to\Godot_v4.4.1-stable_mono_win64.exe", "Machine")
Additional Resources
- GdUnit4 Documentation
- Godot C# Documentation
- GdUnit4Net Documentation
- Project contributing.md for commit conventions and project guidelines
This documentation should help you effectively set up your development environment and tests. Don't hesitate to ask if you have specific questions about any of these aspects!