bunit-blazor-testing
Comprehensive guidance for writing Blazor component tests using bUnit with TUnit as the test runner. Covers in-memory rendering, component parameters, cascading values, service mocking, event testing, semantic HTML assertions, and snapshot testing.
- License 许可
- GPL-3.0
- 最近更新
- 2026-06-06
- Stars
- ★ 0
- Forks
- 0
01 前言 Front Matter
- name
- bunit-blazor-testing
- description
- Comprehensive guidance for writing Blazor component tests using bUnit with TUnit as the test runner. Covers in-memory rendering, component parameters, cascading values, service mocking, event testing, semantic HTML assertions, and snapshot testing.
03 SKILL.md 正文
bunit-blazor-testing
This skill guides the sqa-engineer to write Blazor component tests using bUnit — an in-memory Blazor component testing library — with TUnit as the test runner. bUnit renders Blazor components without a real browser, making tests fast and deterministic.
When to use: Use this skill for testing Blazor components in isolation (unit/component tests). For full browser end-to-end tests of a Blazor app, use
tunit-playwright-ui-testinginstead.
Phase 0 — Context Load (silent)
- Read
CLAUDE.mdand project conventions. - Load memory:
Skill("manage-memory", args: "sqa-engineer"). - Locate Blazor components under test.
- Identify the test project (create
*.BlazorTestsif needed).
Phase 1 — NuGet Setup
<PackageReference Include="bunit" Version="1.*" />
<PackageReference Include="TUnit" Version="*" />
<PackageReference Include="TUnit.Assertions" Version="*" />
<PackageReference Include="Bogus" Version="*" />
Note: bUnit ships a single bunit meta-package. It targets net8.0 and net9.0; confirm compatibility with net10.0 before pinning. If the project is on net10.0, you may need <TargetFramework>net9.0</TargetFramework> for the test project until bUnit releases net10.0 support, or use a preview build.
Phase 2 — TestContext Setup
bUnit provides Bunit.TestContext which renders components in an in-memory Blazor host:
using Bunit;
using TUnit.Core;
[TestClass]
public class WeatherCardComponentTests : IDisposable
{
private readonly TestContext _ctx = new();
[Test]
[DisplayName("[AC-5] WeatherCard shows temperature in Celsius")]
public async Task WeatherCard_WhenTemperatureProvided_ShowsCelsiusValue()
{
// Arrange
IRenderedComponent<WeatherCard> cut = _ctx.RenderComponent<WeatherCard>(parameters => parameters
.Add(p => p.TemperatureC, 22)
.Add(p => p.Summary, "Warm"));
// Act — component is already rendered
// Assert
cut.Find(".temperature").TextContent.Should().Contain("22");
}
public void Dispose() => _ctx.Dispose();
}
Key types:
TestContext— the in-memory Blazor host. Always dispose it after the test class.IRenderedComponent<TComponent>— a rendered component handle.cut— "component under test" (standard bUnit convention).
Phase 3 — Component Parameters
// Typed parameters (preferred)
_ctx.RenderComponent<MyComponent>(parameters => parameters
.Add(p => p.Title, "Hello")
.Add(p => p.IsVisible, true)
.Add(p => p.Items, new List<string> { "a", "b" })
.Add(p => p.OnClick, EventCallback.Factory.Create(this, HandleClick)));
// Child content / RenderFragment
_ctx.RenderComponent<MyComponent>(parameters => parameters
.AddChildContent("<p>Child content</p>"));
Phase 4 — Service Mocking and DI
Register services in the bUnit TestContext's Services collection before rendering:
// Register a real service
_ctx.Services.AddScoped<IWeatherService, WeatherService>();
// Register a mock (using TUnit.Mocks or hand-rolled)
WeatherServiceMock mockWeatherSvc = new();
_ctx.Services.AddScoped<IWeatherService>(_ => mockWeatherSvc);
// Register a Bogus-generated stub
_ctx.Services.AddSingleton(new FakeWeatherService(new Faker()));
Pattern: full DI registration
_ctx.Services.AddSingleton<IConfiguration>(new ConfigurationBuilder().Build());
_ctx.Services.AddHttpClient();
// ...add anything the component's DI graph requires
Phase 5 — Cascading Values
_ctx.RenderComponent<MyComponent>(parameters => parameters
.AddCascadingValue("ThemeName", "dark")
.AddCascadingValue(new AppState { IsLoggedIn = true }));
Phase 6 — Event Testing
Test user interactions by finding elements and triggering events:
[Test]
public async Task SubmitButton_WhenClicked_CallsOnSubmitCallback()
{
bool submitted = false;
IRenderedComponent<ContactForm> cut = _ctx.RenderComponent<ContactForm>(parameters => parameters
.Add(p => p.OnSubmit, EventCallback.Factory.Create(this, () => submitted = true)));
// Act
cut.Find("button[type=submit]").Click();
// Assert
await Assert.That(submitted).IsTrue();
}
Common event helpers:
.Click()— mouse click.Input(value)— input event with value.Change(value)— change event.KeyPress(key)— keyboard eventcut.InvokeAsync(() => component.SomeMethod())— trigger state changes
Phase 7 — Semantic HTML Assertions
bUnit provides semantic HTML comparison that ignores whitespace and attribute order:
// Assert markup matches expected HTML
cut.MarkupMatches("<div class=\"card\"><h1>Title</h1></div>");
// Assert element presence
cut.Find("h1").TextContent.Should().Be("Weather Forecast");
// Assert element count
cut.FindAll("li").Count.Should().Be(5);
// Assert element has CSS class
cut.Find(".alert").ClassList.Should().Contain("alert-danger");
// Assert element attribute
cut.Find("input").GetAttribute("disabled").Should().NotBeNull();
Phase 8 — Snapshot Testing
For components where the full markup is the contract:
[Test]
public void WeatherCard_RendersCorrectMarkup()
{
IRenderedComponent<WeatherCard> cut = _ctx.RenderComponent<WeatherCard>(
p => p.Add(c => c.Summary, "Mild"));
// Saves snapshot on first run; asserts equality on subsequent runs
cut.SaveSnapshot();
}
Snapshots are saved to
__snapshots__/next to the test file. Commit them to source control. Re-generate withdotnet test -- bunit:updateSnapshots=truewhen intentional markup changes are made.
Phase 9 — TUnit-Specific Patterns
Since TUnit replaces xUnit/NUnit as the runner:
// Use [TestClass] not [Fact] or [Test] on classes
// Use [Test] on methods (same as TUnit convention for other test types)
// Use TUnit.Assertions.Should for all assertions on bUnit output
// Use Assert.Multiple() to collect all failures
[Test]
public async Task MyComponent_RendersAllItems()
{
IRenderedComponent<ItemList> cut = _ctx.RenderComponent<ItemList>(
p => p.Add(c => c.Items, ["A", "B", "C"]));
await Assert.Multiple(async () =>
{
await Assert.That(cut.FindAll("li")).HasCount().EqualTo(3);
await Assert.That(cut.Find("li:first-child").TextContent).IsEqualTo("A");
});
}
Phase 10 — Running Tests
dotnet test --filter "FullyQualifiedName~BlazorTests"
Best Practices
| Do | Don't |
|---|---|
Test one behavior per [Test] |
Test multiple behaviors in one test |
Use cut.Find() with semantic selectors |
Use brittle positional selectors |
Dispose TestContext after each test class |
Leak the bUnit host |
Register mocks in _ctx.Services |
Use static/global mocks |
Use MarkupMatches() for structural assertions |
Use raw string equality on HTML |
| Trace each test to an AC | Write untargeted "just render it" tests |
04 同仓库更多 Skills
write-tests
Test implementation skill for HypermediaEngine. Takes a confirmed test plan from design-test-cases and produces compilable, runnable xUnit tests following project conventions. Validates coverage quality through mutation testing. Invoked by the sqa-engineer agent after the test plan is approved.
write-documentation
Structured documentation writing workflow for HypermediaEngine. Writes or updates README.md files at the repo root and in each project directory (src/, tests/, samples/, etc.) based on current implementation, ADRs, and architecture decisions.
write-adr
Writes a numbered Architecture Decision Record (ADR) for a significant architectural choice in the HypermediaEngine project. Invoked by the software-architect agent whenever a consequential design decision is made or ratified. Saves the ADR to docs/architecture/decisions/.
tunit-playwright-ui-testing
Comprehensive guidance for writing coded end-to-end UI tests in C# using TUnit.Playwright. Tests inherit from PageTest — the base class manages all browser lifecycle. Covers NuGet setup, PageTest inheritance, Page Object Model, browser configuration, parallelism limiting, and CI integration. Based on the official TUnit docs at https://tunit.dev/docs/examples/playwright.
triage
Task classification, decomposition, dependency mapping, and routing skill. Invoked by the triage-agent to break any user request into discrete, independently-routable work items with assigned priorities and agent chains.
system-design
Expert system design skill. Use PROACTIVELY to design systems, review architectures, and assist software engineers in learning design principles and implementing them in the code with design patterns.