Blazor Testing and Diagnostics

This section documents ASP.NET Core on .NET 10 (LTS), the current release — the minimal hosting model, the middleware pipeline, dependency injection, Minimal APIs, MVC & Razor Pages, Blazor with the current render modes, SignalR and gRPC, EF Core, ASP.NET Core Identity and policy-based authorization, output caching, rate limiting, and Native-AOT-aware building — as described by the official documentation at Microsoft Learn, which is the reference these pages are written and verified against.

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. .NET ships a major release every November and its APIs continue to evolve: the examples here target .NET 10 / C# 14.

This section’s bibliography lists the reference material consulted while preparing these pages.

Component-level testing, browser debugging, and production diagnostics all work somewhat differently in Blazor than in a request/response MVC app, because a component keeps running (and can be observed) across many renders. See Testing for the general ASP.NET Core testing material this page extends.

bUnit: test project setup

<!-- MyApp.Tests.csproj -->
<PackageReference Include="bunit" Version="1.*" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.*" />
<PackageReference Include="xunit" Version="2.*" />

bUnit renders a component in an in-memory test context — no browser — and gives back a wrapper for inspecting and interacting with the rendered markup.

Rendering components and querying markup

public sealed class CounterTests : TestContext
{
    [Fact]
    public void Increment_IncreasesCount()
    {
        var cut = RenderComponent<Counter>();

        cut.Find("button").Click();

        cut.Find("p").MarkupMatches("<p>Clicked 1 times</p>");
    }
}

Find returns the first matching element (throws if none); FindAll returns every match. cut.Markup is the component’s current rendered HTML, useful for snapshot-style assertions when MarkupMatches is too strict.

Triggering events

cut.Find("input").Change("new value");     // fires the bound change event
cut.Find("form").Submit();
await cut.InvokeAsync(() => cut.Instance.SomeAsyncMethod());

InvokeAsync on the rendered component dispatches onto the render’s synchronization context, needed when calling a method that itself calls StateHasChanged.

Mocking services and IJSRuntime

var cut = RenderComponent<ProductCard>(parameters => parameters
    .Add(p => p.Product, new ProductDto(1, "Widget", 9.99m)));

Services.AddSingleton(Substitute.For<IProductService>());   // register a test double before rendering

JSInterop.SetupVoid("navigator.clipboard.writeText", _ => true);   // stub a JS interop call

TestContext.JSInterop provides Strict or Loose (default) modes — Strict throws on any unconfigured JS call, catching an interop call the test forgot to stub instead of silently no-op-ing.

Testing authorization

var authContext = this.AddTestAuthorization();
authContext.SetAuthorized("alice");
authContext.SetPolicies("AdminOnly");

var cut = RenderComponent<AdminPanel>();

Bunit.TestDoubles’ `AddTestAuthorization fakes AuthenticationStateProvider so <AuthorizeView>/[Authorize]-gated components can be tested without a real sign-in flow.

See bUnit documentation (the community-maintained testing library referenced from Test Razor components in ASP.NET Core Blazor).

End-to-end testing with Playwright

bUnit tests components in isolation; Playwright drives a real browser against a running app, exercising actual render modes, JS interop, and SignalR circuits end to end:

await using var browser = await playwright.Chromium.LaunchAsync();
var page = await browser.NewPageAsync();
await page.GotoAsync("https://localhost:5001/counter");
await page.ClickAsync("button");
await Expect(page.Locator("p")).ToContainTextAsync("Clicked 1 times");

Use it for interaction flows a component test cannot observe — an actual reconnect after a dropped circuit, a real WebAssembly boot, cross-component/page navigation.

Debugging Server vs. WebAssembly

  • Interactive Server debugs like any other ASP.NET Core app: attach the debugger to the server process; breakpoints in component code hit as requests/events come in over the circuit.

  • Interactive WebAssembly debugs through the browser’s own DevTools (Chrome/Edge) with .NET debugging support, or through the IDE’s browser-attached debugger — source maps let breakpoints hit in C# rather than the compiled WASM.

  • Hot Reload applies most code edits to a running app without a full rebuild/restart, for both render modes; WasmEnableHotReload controls whether it is available in a WebAssembly-published/CI-optimized build (it is enabled by default for local dotnet watch development).

Metrics, tracing, and Event Pipe diagnostics (.NET 10)

Blazor emits System.Diagnostics.Metrics-based metrics (circuit counts, connection duration, navigation/render timings) and distributed-tracing spans that flow into the same OpenTelemetry pipeline as the rest of the app — see Error Handling, Logging, and Observability for wiring up exporters. .NET 10 exposes these, along with lower-level runtime Event Pipe events, so a circuit’s render/JS-interop latency can be correlated with the rest of the request pipeline in the same trace instead of appearing as an unexplained gap.

Common production issues

Symptom Likely cause

Server memory grows with concurrent users

Circuit state (component instances, subscribed events, cached data in scoped services) accumulates per connected user under Interactive Server — dispose subscriptions and avoid large per-circuit caches.

Stale/duplicated UI updates

A missed @key on a re-ordered list, or StateHasChanged called from a background thread without InvokeAsync — see Blazor Components and Lifecycle.

"Attempting to reconnect…​" loops

Load balancer/proxy not configured for sticky sessions or WebSocket passthrough on the circuit’s SignalR connection — see Deployment.

Slow first interaction

A large, non-AOT WebAssembly payload with no lazy loading — see Blazor WebAssembly, Hybrid, and Deployment.