Skip to content

Integration MSTest

aryehcitron@gmail.com edited this page May 24, 2026 · 5 revisions

Integration: MSTest

Local integration guide: A copy of this guide is also available at docs/integration-mstest.md.


Overview

This guide walks you through integrating Kronikol with MSTest. After completing this guide, your MSTest tests will automatically generate:

  • PlantUML sequence diagrams from HTTP traffic between your service and its dependencies
  • HTML reports with embedded diagrams
  • YAML specification files

Prerequisites

  • .NET 10.0 SDK or later
  • An ASP.NET Core API project to test (your "Service Under Test")
  • Basic familiarity with MSTest

Step 1: Create the Test Project

Create a new MSTest test project:

dotnet new mstest -n MyApi.Tests.Component

Step 2: Install NuGet Packages

dotnet add package Kronikol.MSTest
dotnet add package Microsoft.AspNetCore.Mvc.Testing
dotnet add package Microsoft.NET.Test.Sdk
dotnet add package MSTest

Step 3: Create the Test Run Setup/Teardown

MSTest uses [AssemblyInitialize] and [AssemblyCleanup] for global setup and teardown.

Infrastructure/TestRun.cs:

using Kronikol;
using Kronikol.MSTest;

namespace MyApi.Tests.Component.Infrastructure;

[TestClass]
public class TestRun : DiagrammedTestRun
{
    [AssemblyInitialize]
    public static void AssemblyInitialize(TestContext context)
    {
        Setup();
        // Optional: start any HTTP fakes here
    }

    [AssemblyCleanup]
    public static void AssemblyCleanup()
    {
        EndRunTime = DateTime.UtcNow;

        MSTestReportGenerator.CreateStandardReportsWithDiagrams(
            TestContexts,
            StartRunTime,
            EndRunTime,
            new ReportConfigurationOptions
            {
                SpecificationsTitle = "My API Specifications"
            });

        // Optional: dispose HTTP fakes here
    }
}

Critical points:

  • [AssemblyInitialize] and [AssemblyCleanup] must be static methods in a class marked with [TestClass].
  • Call Setup() in [AssemblyInitialize] — this records the StartRunTime.
  • Report generation happens in [AssemblyCleanup] after all tests have run.

Step 4: Create the Base Fixture

Infrastructure/BaseFixture.cs:

using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.AspNetCore.TestHost;
using Kronikol.MSTest;

namespace MyApi.Tests.Component.Infrastructure;

public abstract class BaseFixture : DiagrammedComponentTest, IDisposable
{
    private static readonly WebApplicationFactory<Program>? SFactory;
    protected HttpClient Client { get; }

    private const string ServiceUnderTestName = "My API";

    static BaseFixture()
    {
        SFactory = new WebApplicationFactory<Program>().WithWebHostBuilder(builder =>
        {
            builder.ConfigureTestServices(services =>
            {
                services.TrackDependenciesForDiagrams(new MSTestTestTrackingMessageHandlerOptions
                {
                    CallerName = ServiceUnderTestName,
                    PortsToServiceNames =
                    {
                        { 80, ServiceUnderTestName },
                        { 5001, "Downstream Service A" }
                    }
                });
            });
        });
    }

    protected BaseFixture()
    {
        Client = SFactory!.CreateTestTrackingClient(
            new MSTestTestTrackingMessageHandlerOptions
            {
                FixedNameForReceivingService = ServiceUnderTestName
            });
    }

    public void Dispose() => Client.Dispose();
}

Key points:

  • DiagrammedComponentTest provides [TestInitialize] (sets the async-local test context) and [TestCleanup] (enqueues test metadata for report collection).
  • MSTestTestTrackingMessageHandlerOptions uses the async-local TestContext to resolve the current test's identity.

Step 5: Write Test Scenarios

Tests are written as regular MSTest [TestMethod] methods. Use the [Endpoint] and [HappyPath] attributes to add metadata for the report.

Scenarios/Cake_Feature.cs:

using Kronikol.MSTest;

namespace MyApi.Tests.Component.Scenarios;

[Endpoint("/cake")]
public partial class Cake_Feature
{
    [TestMethod]
    [HappyPath]
    public async Task Calling_Create_Cake_Endpoint_Returns_Cake()
    {
        await Given_a_valid_post_request_for_the_Cake_endpoint();
        await When_the_request_is_sent_to_the_cake_post_endpoint();
        await Then_the_response_should_be_successful();
    }

    [TestMethod]
    public async Task Calling_Create_Cake_Endpoint_Without_Eggs_Returns_Bad_Request()
    {
        await Given_a_valid_post_request_for_the_Cake_endpoint();
        await But_the_request_body_is_missing_eggs();
        await When_the_request_is_sent_to_the_cake_post_endpoint();
        await Then_the_response_http_status_should_be_bad_request();
    }
}

Scenarios/Cake_Feature.steps.cs:

using System.Net;
using System.Net.Http.Json;
using MyApi.Tests.Component.Infrastructure;

namespace MyApi.Tests.Component.Scenarios;

[TestClass]
public partial class Cake_Feature : BaseFixture
{
    private HttpResponseMessage? _response;

    private async Task Given_a_valid_post_request_for_the_Cake_endpoint()
    {
        // Build your request using Client
    }

    private async Task But_the_request_body_is_missing_eggs()
    {
        // Modify request
    }

    private async Task When_the_request_is_sent_to_the_cake_post_endpoint()
    {
        _response = await Client.PostAsJsonAsync("cake", /* request */);
    }

    private async Task Then_the_response_should_be_successful()
    {
        _response!.StatusCode.Should().Be(HttpStatusCode.OK);
    }

    private async Task Then_the_response_http_status_should_be_bad_request()
    {
        _response!.StatusCode.Should().Be(HttpStatusCode.BadRequest);
    }
}

Step 6: Run the Tests

dotnet test

After the tests complete, check the bin/Debug/net10.0/Reports/ folder:

File Description
Specifications.html HTML specifications with embedded PlantUML sequence diagrams
TestRunReport.html HTML test run report with diagrams and execution summary
Specifications.yml YAML specifications

Using PlantUML Overrides

You can customise diagrams within a test using TrackingDiagramOverride:

using Kronikol.MSTest;

// Insert a delimiter between multiple requests in the diagram
TrackingDiagramOverride.InsertTestDelimiter("Step 1");

// Insert raw PlantUML markup
TrackingDiagramOverride.InsertPlantUml("note over MyApi : Custom note");

// Override the start/end of diagram generation
TrackingDiagramOverride.StartOverride();
TrackingDiagramOverride.EndOverride();

// Explicitly mark the beginning of the setup phase (produces a visual separator in the diagram)
TrackingDiagramOverride.StartSetup();

// Explicitly mark the boundary between setup and action phases
TrackingDiagramOverride.StartAction();

Setup separation: When SeparateSetup = true is set on ReportConfigurationOptions, HTTP calls made before StartAction() are wrapped in a visual "Setup" partition in the diagram. Call StartSetup() to explicitly mark the beginning of the setup phase.


CurrentTestInfo

The Kronikol.MSTest package provides a CurrentTestInfo static class whose Fetcher property returns a Func<(string Name, string Id)> delegate that resolves the current test's identity. This is used by all extension CurrentTestInfoFetcher options:

using Kronikol.MSTest;

options.CurrentTestInfoFetcher = CurrentTestInfo.Fetcher;

Architecture Summary

┌─────────────────────────────────┐
│           TestRun               │  ← [TestClass] with [AssemblyInitialize]/[AssemblyCleanup]
│     : DiagrammedTestRun         │     Generates reports in [AssemblyCleanup]
└─────────────────────────────────┘
              │
              ▼
┌─────────────────────────────────┐
│          BaseFixture            │  ← Creates tracked HttpClient
│   : DiagrammedComponentTest     │     Sets async-local context on [TestInitialize]
│   IDisposable                   │     Enqueues MSTestScenarioInfo on [TestCleanup]
└─────────────┬───────────────────┘
              │ inherited by
              ▼
┌─────────────────────────────────┐
│  Cake_Feature : BaseFixture     │  ← Your test class with [TestMethod] methods
│  [TestClass]                    │
│  [Endpoint("/cake")]            │
└─────────────────────────────────┘

Notes

  • MSTest's TestContext is not statically accessible like NUnit's TestContext.CurrentContext or xUnit v3's TestContext.Current. Kronikol.MSTest uses an AsyncLocal<TestContext> internally to make the test context available to the HTTP tracking pipeline.
  • The [TestInitialize] and [TestCleanup] methods are provided by DiagrammedComponentTest. If your base fixture needs its own initialize/cleanup logic, call the base methods.
  • For data-driven tests ([DataRow]), each data row is tracked as a separate scenario in the report.

Customisation Options

ReportConfigurationOptions

Property Default Description
SpecificationsTitle "Service Specifications" Title shown at the top of reports
PlantUmlServerBaseUrl "https://plantuml.com/plantuml" PlantUML server URL
HtmlSpecificationsFileName "Specifications" Output filename for specs HTML
HtmlTestRunReportFileName "TestRunReport" Output filename for test run HTML
YamlSpecificationsFileName "Specifications" Output filename for YAML specs
HtmlSpecificationsCustomStyleSheet Stylesheets.VioletThemeStyleSheet Custom CSS appended to specs HTML
ExcludedHeaders [] HTTP headers to exclude from diagrams
SeparateSetup false When true, HTTP calls made before StartAction() are wrapped in a visual "Setup" partition in the diagram
HighlightSetup true When true (and SeparateSetup is enabled), the setup partition is rendered with a background colour

MSTestTestTrackingMessageHandlerOptions

Property Default Description
CallerName "Caller" Name of the calling service in diagrams
FixedNameForReceivingService null When set, all downstream services use this name
PortsToServiceNames {} Maps ports to human-readable downstream service names
ClientNamesToServiceNames {} Maps named HTTP client names to service names

Faking Downstream Dependencies (Correctly)

When your SUT calls downstream HTTP services, those calls must flow through TestTrackingMessageHandler to produce proper HTTP-style diagram arrows (with method, status code, headers, body). Do not mock service client interfaces and use MessageTracker to manually log HTTP interactions — this produces event-style (blue) arrows that are misleading.

Recommended approaches:

See Tracking Dependencies#faking-dependencies-getting-proper-http-tracking for detailed examples of each approach.

Home


Demo


Getting Started

Common Tasks

Integration Guides

Extensions

Configuration

Features

Reference

Clone this wiki locally