The Test SDK (ANcpLua.NET.Sdk.Test) provides xUnit v3 with Microsoft Testing Platform and injects base classes for integration testing.
The SDK uses xUnit v3 with Microsoft Testing Platform instead of VSTest.
Auto-Configuration
MTP requires test projects to output executables. The SDK handles this automatically when you reference xunit.v3.mtp-v2:
<!-- SDK auto-detects this and sets OutputType=Exe -->
< PackageReference Include = "xunit.v3.mtp-v2" />
CLI Syntax
MTP uses different command syntax than VSTest:
.NET 10+ (MTP native)
.NET 8/9 (MTP opt-in)
VSTest syntax (BANNED)
# Run all tests
dotnet test
# Run with filter (no -- separator needed)
dotnet test --filter-method "*MyTest*"
# List tests
dotnet test --list-tests
# Generate TRX report
dotnet test --report-trx
VSTest syntax causes exit code 5 with MTP. Use --filter-method instead
of --filter "FQN~" and --report-trx instead of --logger trx.
.NET 10 includes native MTP support via global.json. The -- separator is
only needed for .NET 8/9 with explicit MTP opt-in.
Troubleshooting
Exit Code Cause Fix 5 Unknown CLI option Use MTP syntax (--filter-method), not VSTest (--filter "FQN~") 8 Zero tests discovered Check filter pattern; ensure test methods are public N/A Tests not found Verify xunit.v3.mtp-v2 reference; SDK sets OutputType=Exe
xUnit v3 TestContext
xUnit v3 replaces constructor-injected ITestOutputHelper with ambient TestContext. Access test output anywhere without passing it through constructor chains.
Before vs After
Aspect xUnit v2 (Before) xUnit v3 (After) Test constructor params fixture, testOutputHelper fixture Fields to store 2 1 Builder constructor params 4 3 Boilerplate per test class ~3 lines 0 How output is accessed Passed through chain Ambient TestContext.Current
Migration Example
xUnit v2 (before)
xUnit v3 (after)
public class MyApiTests : IntegrationTestBase < Program >
{
private readonly ITestOutputHelper _output ;
private readonly MyFixture _fixture ;
public MyApiTests ( MyFixture fixture , ITestOutputHelper output )
{
_fixture = fixture ;
_output = output ;
}
[ Fact ]
public async Task Get_ReturnsSuccess ()
{
_output . WriteLine ( "Starting test..." );
var response = await Client . GetAsync ( "/api/items" );
response . EnsureSuccessStatusCode ();
}
}
TestContext.Current is available anywhere during test execution - in test
methods, helper classes, or builder patterns - without explicit parameter
passing.
IntegrationTestBase
In-memory TestServer for fast API testing.
public class MyApiTests : IntegrationTestBase < Program >
{
[ Fact ]
public async Task Get_ReturnsSuccess ()
{
var response = await Client . GetAsync ( "/api/items" );
response . EnsureSuccessStatusCode ();
}
}
```
Uses ` WebApplicationFactory < TProgram > ` internally .
## KestrelTestBase
Real Kestrel server for HTTP/2, WebSockets, SSE, or Playwright.
```csharp
public class WebSocketTests : KestrelTestBase<Program>
{
[Fact]
public async Task WebSocket_ConnectsSuccessfully()
{
var ws = new ClientWebSocket ();
await ws . ConnectAsync ( new Uri ( $" { BaseAddress } /ws" ), CancellationToken . None );
}
}
```
Starts on random port via ` UseKestrel ( 0 )`.
## FakeLogger
See [ Extensions ]( / sdk / extensions ) for `FakeLogCollector` test helpers.
## AOT/Trim Testing
The SDK provides infrastructure for testing Native AOT and trimmed applications. Tests run as separate processes with `PublishAot=true` or `PublishTrimmed=true` to verify runtime behavior.
### Enabling AOT Testing
```xml
<Project Sdk="ANcpLua.NET.Sdk.Test">
<PropertyGroup>
<EnableAotTesting>true</EnableAotTesting>
</PropertyGroup>
</Project>
```
This automatically references `ANcpLua.AotTesting.Attributes`.
### Writing AOT Tests
AOT tests are methods returning `int` with exit code `100` for success:
```csharp
using ANcpLua.AotTesting;
public class MyAotTests
{
[ AotTest ]
public static int BasicAotTest ()
{
var list = new List < int > { 1 , 2 , 3 };
if ( list . Sum () != 6 )
{
Console . Error . WriteLine ( "FAIL: Sum incorrect" );
return 1 ;
}
return 100 ; // Success
}
[ TrimTest ( TrimMode = TrimMode . Full )]
public static int VerifyTrimmingWorks ()
{
// Test trimming behavior
return 100 ;
}
}
```
### Test Attributes
< AccordionGroup >
< Accordion title = "[AotTest]" >
Publishes with ` PublishAot = true ` and executes the native binary .
``` csharp
[ AotTest (
SkipOnPlatform = "osx" , // Skip on macOS
RuntimeIdentifier = "win-x64" , // Override RID
DisabledFeatureSwitches = new [] { FeatureSwitches . JsonReflection },
Configuration = "Release" , // Default: Release
TimeoutSeconds = 300 // Default: 5 minutes
)]
public static int MyAotTest () = > 100 ;
```
</ Accordion >
< Accordion title = "[TrimTest]" >
Publishes with ` PublishTrimmed = true ` for testing IL trimming .
``` csharp
[ TrimTest (
TrimMode = TrimMode . Full , // Full or Partial
SkipOnPlatform = "linux" ,
TimeoutSeconds = 180
)]
public static int MyTrimTest () = > 100 ;
```
</ Accordion >
< Accordion title = "[TrimSafe] / [AotSafe]" >
In - process markers verified by analyzers ([ AL0043 ]( / analyzers / rules / AL0043 ), [ AL0044 ]( / analyzers / rules / AL0044 )) :
``` csharp
[ TrimSafe ] // Analyzer verifies no RequiresUnreferencedCode calls
public void ProcessData < T >( T data ) { }
[ AotSafe ] // Analyzer verifies no RequiresDynamicCode calls
public void CreateService () { }
```
</ Accordion >
</ AccordionGroup >
### TrimAssert Helpers
Verify types are trimmed or preserved at runtime :
``` csharp
[ TrimTest ( TrimMode = TrimMode . Full )]
public static int VerifyUnusedTypeTrimmed ()
{
// Assert type was trimmed away
TrimAssert . TypeTrimmed (
"MyNamespace.UnusedService" ,
"MyAssembly" );
// Assert essential type survives
TrimAssert . TypePreserved (
"System.Collections.Generic.List`1" ,
"System.Private.CoreLib" );
return 100 ;
}
```
< Warning >
Do not use ` typeof ( X )` in assertions — it roots the type and prevents trimming .
Use string - based ` TrimAssert ` methods instead .
</ Warning >
### Feature Switches
Disable runtime features during tests :
``` csharp
[ AotTest ( DisabledFeatureSwitches = new [] {
FeatureSwitches . JsonReflection ,
FeatureSwitches . EventSourceSupport
})]
public static int TestWithoutReflection () = > 100 ;
```
| Constant | Feature Switch |
|----------|----------------|
| ` JsonReflection ` | ` System . Text . Json . JsonSerializer . IsReflectionEnabledByDefault ` |
| ` DebuggerSupport ` | ` System . Diagnostics . Debugger . IsSupported ` |
| ` EventSourceSupport ` | ` System . Diagnostics . Tracing . EventSource . IsSupported ` |
| ` MetricsSupport ` | ` System . Diagnostics . Metrics . Meter . IsSupported ` |
| ` XmlSerialization ` | ` System . Xml . XmlSerializer . IsEnabled ` |
| ` BinaryFormatter ` | ` System . Runtime . Serialization . EnableUnsafeBinaryFormatterSerialization ` |
| ` InvariantGlobalization ` | ` System . Globalization . Invariant ` |
| ` HttpActivityPropagation ` | ` System . Net . Http . EnableActivityPropagation ` |
| ` StartupHooks ` | ` System . StartupHookProvider . IsSupported ` |
### How It Works
1 . ** Discovery **: MSBuild target scans for `[AotTest]`/`[TrimTest]` methods
2. **Project Generation**: Creates temporary `.csproj` for each test
3. **Publish**: Runs `dotnet publish` with AOT/Trim flags
4. **Execute**: Runs the published executable
5. **Validate**: Checks exit code (100 = success)
### Exit Code Convention
| Code | Meaning |
|------|---------|
| 100 | Success |
| 1 - 99 | Test failure |
| - 1 | ` TrimAssert . TypeTrimmed ` failed ( type exists ) |
| - 2 | ` TrimAssert . TypePreserved ` failed ( type missing ) |
### Related Analyzers
| Rule | Description |
|------|-------------|
| [ AL0041 ]( / analyzers / rules / AL0041 ) | `[ AotTest ]` / `[ TrimTest ]` must return ` int ` |
| [ AL0042 ]( / analyzers / rules / AL0042 ) | Should return 100 for success |
| [ AL0043 ]( / analyzers / rules / AL0043 ) | `[ TrimSafe ]` cannot call `[ RequiresUnreferencedCode ]` |
| [ AL0044 ]( / analyzers / rules / AL0044 ) | `[ AotSafe ]` cannot call `[ RequiresDynamicCode ]` |