Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ title: Build extensions for Microsoft.Testing.Platform (MTP)
description: Learn how to create in-process and out-of-process extensions for Microsoft.Testing.Platform (MTP).
author: MarcoRossignoli
ms.author: mrossignoli
ms.date: 02/24/2026
ms.date: 06/02/2026
ai-usage: ai-assisted
---

Expand All @@ -17,6 +17,9 @@ For the full extension point summary and in-process/out-of-process concepts, see

The testing platform provides additional extensibility points that allow you to customize the behavior of the platform and the test framework. These extensibility points are optional and can be used to enhance the testing experience.

> [!TIP]
> Each extension shown in this article includes a manual registration snippet (for example, `builder.TestHost.AddDataConsumer(...)`). If you're shipping your extension as a NuGet package, you can let consumers skip the manual call by exposing a `TestingPlatformBuilderHook` and a small MSBuild props file. The auto-generated entry point will then invoke your hook automatically. For details, see [Auto-register your extension with `TestingPlatformBuilderHook`](#auto-register-your-extension-with-testingplatformbuilderhook).

### The `ICommandLineOptionsProvider` extensions

> [!NOTE]
Expand Down Expand Up @@ -499,6 +502,93 @@ The `ITestHostProcessInformation` interface provides the following details:
* `ExitCode`: The exit code of the process. This value is only available within the `OnTestHostProcessExitedAsync` method. Attempting to access it within the `OnTestHostProcessStartedAsync` method will result in an exception.
* `HasExitedGracefully`: A boolean value indicating whether the test host has crashed. If true, it signifies that the test host did not exit gracefully.

## Auto-register your extension with `TestingPlatformBuilderHook`

Every preceding extension section shows a *manual* registration call (for example, `builder.TestHost.AddDataConsumer(...)`). Asking consumers to edit their `Main` method is a poor onboarding experience. The [`Microsoft.Testing.Platform.MSBuild`](https://www.nuget.org/packages/Microsoft.Testing.Platform.MSBuild) package solves this by generating a `SelfRegisteredExtensions.AddSelfRegisteredExtensions(builder, args)` method that runs from the autogenerated entry point. To plug your extension into that generated method, ship two artifacts in your NuGet package:

- A public static `TestingPlatformBuilderHook` class with an `AddExtensions` method that registers your extension.
- An MSBuild props file that declares a `<TestingPlatformBuilderHook>` item pointing at that class.

When a consumer installs your package, the MSBuild integration picks up the item and generates the call into your hook, and your extension is registered with no code changes on the consumer side.

> [!NOTE]
> Auto-registration only works when the consumer has `Microsoft.Testing.Platform.MSBuild` in their project (it's included transitively by MSTest, NUnit, and xUnit runners) and hasn't opted out by setting `<GenerateTestingPlatformEntryPoint>false</GenerateTestingPlatformEntryPoint>`. Consumers who disable the autogenerated entry point still need to call your manual registration API from their `Main` method.

### Create the hook class

Add a `public static class TestingPlatformBuilderHook` in your extension assembly with an `AddExtensions(ITestApplicationBuilder, string[])` method that performs the same registration users would otherwise call manually:

```csharp
using Microsoft.Testing.Platform.Builder;

namespace Contoso.MyExtension;

public static class TestingPlatformBuilderHook
{
public static void AddExtensions(ITestApplicationBuilder testApplicationBuilder, string[] arguments)
=> testApplicationBuilder.AddMyExtension();
}
```

The class name doesn't have to be `TestingPlatformBuilderHook` — the MSBuild item points at it by full type name — but using that name keeps your code consistent with the in-box extensions like `Microsoft.Testing.Extensions.Retry` and `Microsoft.Testing.Extensions.HotReload`.

The method must:

* Be `public static`.
* Have a first parameter of type `Microsoft.Testing.Platform.Builder.ITestApplicationBuilder`.
* Have a second parameter of type `string[]` (the command-line arguments passed to the test host). You can ignore it if your extension doesn't need it.
* Return `void`.

### Declare the MSBuild item

Ship a props file under `buildMultiTargeting/<PackageId>.props` inside your NuGet package. Declare a `<TestingPlatformBuilderHook>` item that points the MSBuild task at your hook class:

```xml
<Project>
<ItemGroup>
<TestingPlatformBuilderHook Include="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx">
<DisplayName>Contoso.MyExtension</DisplayName>
<TypeFullName>Contoso.MyExtension.TestingPlatformBuilderHook</TypeFullName>
</TestingPlatformBuilderHook>
</ItemGroup>
</Project>
```

The metadata is as follows:

* `Include`: A GUID that uniquely identifies your hook. See [The `Include` GUID is a random identifier](#the-include-guid-is-a-random-identifier).
* `DisplayName`: The friendly name shown in MSBuild diagnostic messages when the entry point is generated. Use your package or extension name.
* `TypeFullName`: The fully qualified name of the `TestingPlatformBuilderHook` class you created previously. The MSBuild task uses this to emit `global::Contoso.MyExtension.TestingPlatformBuilderHook.AddExtensions(builder, args);` into the generated entry point.

### The `Include` GUID is a random identifier

The GUID in the `Include` attribute is **not** the same as your extension's [`IExtension.Uid`](./microsoft-testing-platform-architecture.md#the-iextension-interface). It's a registration identifier used by the MSBuild task to deduplicate hooks across NuGet references and (in a few well-known cases) to order them.

When you author a new extension, generate a brand new GUID and hard-code it in your props file. Some ways to generate one:

* PowerShell: `[guid]::NewGuid()`
* Visual Studio: **Tools** > **Create GUID**
* `uuidgen` on Linux and macOS

> [!IMPORTANT]
> Never copy a GUID from another extension's props file (whether shipped by Microsoft or by a third party). Two extensions that share the same `Include` value are treated as duplicates: only one hook is invoked, so your extension silently fails to register.

> [!NOTE]
> Once you ship a GUID, treat it as permanent. Changing it in a later release is harmless on its own, but reusing the *old* value for a different hook in a future package version can confuse consumers who have both versions in their dependency graph during an upgrade.

### Verify the hook is wired up

After installing your package in a test project that uses `Microsoft.Testing.Platform.MSBuild`, build the project and inspect the generated `SelfRegisteredExtensions.g.cs` file under `obj/<Configuration>/<TargetFramework>/`. You should see a call into your hook, for example:

```csharp
public static void AddSelfRegisteredExtensions(this global::Microsoft.Testing.Platform.Builder.ITestApplicationBuilder builder, string[] args)
{
global::Contoso.MyExtension.TestingPlatformBuilderHook.AddExtensions(builder, args);
}
```

If the call is missing, double-check that the props file is packaged under `buildMultiTargeting/` (not `build/`) inside the `.nupkg`, that `DisplayName` and `TypeFullName` metadata are present, and that the consumer hasn't set `<GenerateTestingPlatformEntryPoint>false</GenerateTestingPlatformEntryPoint>`.

## Extensions execution order

The testing platform consists of a [testing framework](./microsoft-testing-platform-architecture-test-framework.md#test-framework-extension) and any number of extensions that can operate [*in-process*](./microsoft-testing-platform-architecture.md#in-process-vs-out-of-process-extensions) or [*out-of-process*](./microsoft-testing-platform-architecture.md#in-process-vs-out-of-process-extensions). This document outlines the **sequence of calls** to all potential extensibility points to provide clarity on when a feature is anticipated to be invoked:
Expand Down
Loading