> ## Documentation Index
> Fetch the complete documentation index at: https://ancplua.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Testing

> xUnit v3 with Microsoft Testing Platform, integration test base classes, and AOT/Trim testing infrastructure.

The Test SDK (`ANcpLua.NET.Sdk.Test`) provides xUnit v3 with Microsoft Testing Platform and injects base classes for integration testing.

## Microsoft Testing Platform (MTP)

The SDK uses [xUnit v3](https://xunit.net/docs/getting-started/v3/cmdline) with [Microsoft Testing Platform](https://learn.microsoft.com/en-us/dotnet/core/testing/microsoft-testing-platform-overview) instead of VSTest.

### Auto-Configuration

MTP requires test projects to output executables. **The SDK handles this automatically** when you reference `xunit.v3.mtp-v2`:

```xml theme={null}
<!-- SDK auto-detects this and sets OutputType=Exe -->
<PackageReference Include="xunit.v3.mtp-v2" />
```

### CLI Syntax

MTP uses different command syntax than VSTest:

<CodeGroup>
  ```bash .NET 10+ (MTP native) theme={null}
  # 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
  ```

  ```bash .NET 8/9 (MTP opt-in) theme={null}
  # Requires -- separator to pass args to test executable
  dotnet test -- --filter-method "*MyTest*"
  dotnet test -- --list-tests
  ```

  ```bash VSTest syntax (BANNED) theme={null}
  # These do NOT work with MTP - exit code 5
  dotnet test --filter "FullyQualifiedName~MyTest"
  dotnet test --logger trx
  ```
</CodeGroup>

<Warning>
  **VSTest syntax causes exit code 5 with MTP.** Use `--filter-method` instead
  of `--filter "FQN~"` and `--report-trx` instead of `--logger trx`.
</Warning>

<Note>
  .NET 10 includes native MTP support via `global.json`. The `--` separator is
  only needed for .NET 8/9 with explicit MTP opt-in.
</Note>

### 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

<CodeGroup>
  ```csharp xUnit v2 (before) theme={null}
  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();
      }

  }

  ```

  ```csharp xUnit v3 (after) theme={null}
  public class MyApiTests : IntegrationTestBase<Program>
  {
      private readonly MyFixture _fixture;

      public MyApiTests(MyFixture fixture) => _fixture = fixture;

      [Fact]
      public async Task Get_ReturnsSuccess()
      {
          TestContext.Current.TestOutputHelper.WriteLine("Starting test...");
          var response = await Client.GetAsync("/api/items");
          response.EnsureSuccessStatusCode();
      }
  }
  ```
</CodeGroup>

<Tip>
  `TestContext.Current` is available anywhere during test execution - in test
  methods, helper classes, or builder patterns - without explicit parameter
  passing.
</Tip>

## IntegrationTestBase

In-memory TestServer for fast API testing.

```csharp theme={null}
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 theme={null}
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 theme={null}
<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 theme={null}
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 theme={null}
    [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 theme={null}
    [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 theme={null}
    [TrimSafe]  // Analyzer verifies no RequiresUnreferencedCode calls
    public void ProcessData<T>(T data) { }

    [AotSafe]   // Analyzer verifies no RequiresDynamicCode calls
    public void CreateService() { }
    ```
  </Accordion>

  <Accordion title="[AotUnsafe]">
    Marks code that intentionally uses AOT-incompatible patterns. [AL0052](/analyzers/rules/AL0052) prevents `[AotSafe]` code from calling `[AotUnsafe]` code, and [AL0053](/analyzers/rules/AL0053) flags unnecessary `[AotUnsafe]` attributes.

    ```csharp theme={null}
    [AotUnsafe("Uses reflection for plugin loading")]
    public void LoadPlugin(string typeName) { }

    [AotSafe]
    public void Process()
    {
        // AL0052: [AotSafe] code must not call [AotUnsafe] code
        // LoadPlugin("MyPlugin");
    }
    ```
  </Accordion>
</AccordionGroup>

### TrimAssert Helpers

Verify types are trimmed or preserved at runtime:

```csharp theme={null}
[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 theme={null}
[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]`       |
| [AL0052](/analyzers/rules/AL0052) | `[AotSafe]` must not call `[AotUnsafe]`               |
| [AL0053](/analyzers/rules/AL0053) | Unnecessary `[AotUnsafe]` attribute                   |
| [AL0094](/analyzers/rules/AL0094) | Avoid `dynamic` keyword in AOT                        |
| [AL0095](/analyzers/rules/AL0095) | Avoid `Expression.Compile()` in AOT                   |
| [AL0101](/analyzers/rules/AL0101) | Avoid `Activator.CreateInstance` in AOT               |
| [AL0102](/analyzers/rules/AL0102) | Avoid `Type.GetType` with dynamic name                |
