| name | policy-emulator-testing |
| description | Conventions and patterns for writing gateway emulator policy handler tests in the Azure API Management policy toolkit. Use this skill when creating or modifying test files in test/Test.Testing/Emulator/Policies/. |
Policy Emulator Testing Patterns
This skill describes how to write tests for gateway emulator policy handlers in the Azure API Management policy toolkit.
Test File — test/Test.Testing/Emulator/Policies/{PolicyName}Tests.cs
File Template
namespace Test.Emulator.Emulator.Policies;
[TestClass]
public class {PolicyName}Tests
{
class Simple{PolicyName} : IDocument
{
public void Inbound(IInboundContext context)
{
context.{PolicyName}(new {PolicyName}Config
{
});
}
public void Outbound(IOutboundContext context) { }
public void Backend(IBackendContext context) { }
public void OnError(IOnErrorContext context) { }
}
[TestMethod]
public void Should{ExpectedBehavior}()
{
var test = new Simple{PolicyName}().AsTestDocument();
test.RunInbound();
test.Context.{assertion};
}
}
Key Conventions
- Namespace:
Test.Emulator.Emulator.Policies (matches existing test convention)
- Class name:
{PolicyName}Tests
- Framework: MSTest (
[TestClass], [TestMethod])
- Assertions: FluentAssertions
- Test document creation:
new SimpleDocument().AsTestDocument() extension method (or new TestDocument(new SimpleDocument()) — both are equivalent)
- Inner document classes implement
IDocument with all four section methods. Unused sections have empty bodies.
- Section execution:
test.RunInbound(), test.RunOutbound(), test.RunBackend(), test.RunOnError()
Required Test Coverage
Each policy must have tests covering:
1. Basic Execution
Create a document with the policy call, run through TestDocument, assert context state changes:
[TestMethod]
public void ShouldModifyContextState()
{
var test = new SimpleSetStatus().AsTestDocument();
test.RunInbound();
test.Context.Response.StatusCode.Should().Be(200);
test.Context.Response.StatusReason.Should().Be("OK");
}
2. Callback Override
Verify that SetupInbound() (or appropriate section) .{PolicyName}().WithCallback(...) correctly overrides default behavior:
[TestMethod]
public void ShouldExecuteCallback()
{
var test = new Simple{PolicyName}().AsTestDocument();
test.SetupInbound()
.{PolicyName}()
.WithCallback((context, config) =>
{
context.Variables["test"] = "value";
});
test.RunInbound();
test.Context.Variables.Should().ContainKey("test")
.WhoseValue.Should().Be("value");
}
3. Predicate-Based Mock
For policies called multiple times with different configs in the same section:
[TestMethod]
public void ShouldSelectCorrectCallback()
{
var test = new Multi{PolicyName}().AsTestDocument();
test.SetupInbound()
.{PolicyName}((ctx, config) => config.SomeProperty == "A")
.WithCallback((ctx, cfg) => { });
test.SetupInbound()
.{PolicyName}((ctx, config) => config.SomeProperty == "B")
.WithCallback((ctx, cfg) => { });
test.RunInbound();
}
4. Pre-configured Context
When the handler depends on existing context state:
[TestMethod]
public void ShouldHandleExistingHeaders()
{
var test = new Simple{PolicyName}().AsTestDocument();
test.Context.Request.Headers["Content-Type"] = ["application/json"];
test.RunInbound();
test.Context.Request.Headers.Should().ContainKey("Content-Type");
}
5. Error/Exception Scenarios
Not all policies support .WithError(). This API is specific to certain mock providers (e.g., MockAuthenticationManagedIdentityProvider). For policies without a dedicated error API, simulate errors via callbacks:
[TestMethod]
public void ShouldSimulateError()
{
var test = new Simple{PolicyName}().AsTestDocument();
test.SetupInbound()
.{PolicyName}()
.WithCallback((context, config) =>
{
context.Response.StatusCode = 500;
context.Response.StatusReason = "Internal Server Error";
throw new FinishSectionProcessingException();
});
test.RunInbound();
test.Context.Response.StatusCode.Should().Be(500);
}
For authentication policies that have .WithError():
[TestMethod]
public void ShouldThrowOnError()
{
var test = new SimpleAuthenticationManagedIdentity().AsTestDocument();
test.SetupInbound()
.AuthenticationManagedIdentity()
.WithError("InternalServerError");
var ex = Assert.ThrowsException<PolicyException>(() => test.RunInbound());
ex.Policy.Should().Be("AuthenticationManagedIdentity");
}
6. Multi-Section Tests
If the policy is available in multiple sections, add one test per section:
[TestMethod]
public void ShouldWorkInOutboundSection()
{
var test = new Outbound{PolicyName}().AsTestDocument();
test.RunOutbound();
test.Context.{assertion};
}
Mock Setup Patterns
Token Provider Hook (Authentication policies)
test.SetupInbound()
.AuthenticationManagedIdentity()
.WithTokenProviderHook((resource, clientId) => $"{resource}{clientId}/token");
Return Value (Authentication, Cache)
test.SetupInbound()
.AuthenticationManagedIdentity()
.ReturnsToken("mock-token");
Cache Store Setup
test.SetupCacheStore()
.WithExternalCacheSetup()
.WithExternalCacheValue("key", "test");
test.SetupCacheStore().WithInternalCacheValue("key", "test");
Response Example Store (MockResponse)
var example = new ResponseExample(200, "{ \"ok\": true }", "application/json");
test.SetupResponseExampleStore().Add(test.Context, example);
Test Infrastructure
Tests rely on shared infrastructure that you do not need to modify:
TestDocument (src/Testing/TestDocument.cs) — Wraps an IDocument and executes sections through proxies. Catches FinishSectionProcessingException gracefully.
.AsTestDocument() extension method — Creates TestDocument from IDocument.
SetupInbound()/SetupOutbound()/etc. — Returns MockPoliciesProvider<TSection> for fluent mock configuration.
GatewayContext (src/Testing/GatewayContext.cs) — Contains mock request/response, variables, and emulator stores.
Usings.cs (test/Test.Testing/Usings.cs) — Global usings for FluentAssertions and Microsoft.VisualStudio.TestTools.UnitTesting.
Build and Test Commands
dotnet test --project test/Test.Testing
dotnet test --project test/Test.Testing --filter "FullyQualifiedName~{PolicyName}Tests"