6.6 KiB
Cross-Cutting Concern: Testing
Project: WPF UI (wpfui) v4.2.0 Last Updated: 2026-02-10
Overview
WPF UI employs a two-tier testing strategy consisting of unit tests for isolated logic verification and integration tests for end-to-end UI automation. The testing infrastructure is intentionally lightweight, reflecting the library's nature as a visual control library where many behaviors require a running WPF application to validate.
Test Pyramid
/ Integration Tests \ 8 tests
/ (FlaUI + Gallery App) \ End-to-end UI automation
/________________________\
/ \
/ Unit Tests \ 6 tests
/ (XUnit + NSubstitute) \ Isolated logic, pure functions
/________________________________\
| Layer | Scope | Framework | Assertion Library | Count |
|---|---|---|---|---|
| Unit Tests | Pure logic, extension methods, animation providers | XUnit 2.9.3 + NSubstitute 5.3.0 | Xunit.Assert |
6 |
| Integration Tests | Window management, navigation, dialogs, title bar | XUnit v3 3.2.0 + FlaUI.UIA3 5.0.0 | AwesomeAssertions 9.3.0 | 8 |
Test Framework Versions
All versions are managed centrally in Directory.Packages.props:
| Package | Version | Purpose |
|---|---|---|
xunit |
2.9.3 | Unit test framework (v2-style API) |
xunit.v3 |
3.2.0 | Integration test framework (v3 with IAsyncLifetime) |
xunit.runner.visualstudio |
3.1.5 | Visual Studio / dotnet test runner |
Microsoft.NET.Test.Sdk |
18.0.0 | .NET test SDK infrastructure |
NSubstitute |
5.3.0 | Mocking library for unit tests |
AwesomeAssertions |
9.3.0 | Fluent assertions (FluentAssertions successor) |
FlaUI.Core |
5.0.0 | UI automation core library |
FlaUI.UIA3 |
5.0.0 | UIA3 automation adapter |
coverlet.collector |
6.0.4 | Code coverage collection |
Test Naming Conventions
Unit Tests
Pattern: MethodName_ExpectedResult_WhenCondition
[Fact]
public void ApplyTransition_ReturnsFalse_WhenDurationIsLessThan10()
Alternative pattern: GivenX_Method_ExpectedResult
[Fact]
public void GivenAllRegularSymbols_Swap_ReturnsValidFilledSymbol()
Integration Tests
Pattern: Subject_ShouldExpectedBehavior_WhenCondition
[Fact]
public async Task CloseButton_ShouldCloseWindow_WhenClicked()
Class Naming
- Unit test classes:
{ClassUnderTest}Tests(e.g.,TransitionAnimationProviderTests) - Integration test classes:
{Feature}Tests(e.g.,TitleBarTests,NavigationTests) - Integration test classes are
sealed; unit test classes are not
Test Directory Structure
graph TD
A["tests/"] --> B["Wpf.Ui.UnitTests/"]
A --> C["Wpf.Ui.Gallery.IntegrationTests/"]
B --> B1["Wpf.Ui.UnitTests.csproj<br/><i>net10.0-windows</i>"]
B --> B2["Animations/"]
B --> B3["Extensions/"]
B --> B4["GlobalUsings.cs"]
B2 --> B2a["TransitionAnimationProviderTests.cs"]
B3 --> B3a["SymbolExtensionsTests.cs"]
C --> C1["Wpf.Ui.Gallery.IntegrationTests.csproj<br/><i>net10.0-windows10.0.26100.0</i>"]
C --> C2["Fixtures/"]
C --> C3["WindowTests.cs"]
C --> C4["TitleBarTests.cs"]
C --> C5["NavigationTests.cs"]
C --> C6["ContentDialogAutomationTests.cs"]
C --> C7["xunit.runner.json"]
C2 --> C2a["UiTest.cs <i>(base class)</i>"]
C2 --> C2b["TestedApplication.cs <i>(app lifecycle)</i>"]
D["src/Wpf.Ui.FlaUI/"] --> D1["AutoSuggestBox.cs<br/><i>Custom FlaUI element</i>"]
style A fill:#f5f5f5,stroke:#333
style B fill:#e3f2fd,stroke:#1565c0
style C fill:#e8f5e9,stroke:#2e7d32
style D fill:#fff3e0,stroke:#e65100
Test Infrastructure
Unit Tests
Unit tests reference the core Wpf.Ui project directly and use NSubstitute for mocking WPF types (e.g., UIElement). Global usings are defined for common namespaces:
System, System.Windows, NSubstitute, Xunit
Source: tests/Wpf.Ui.UnitTests/GlobalUsings.cs
Integration Tests
Integration tests use a custom infrastructure built on FlaUI:
TestedApplication(IAsyncLifetime): Launches and manages the Gallery.exeprocess. Finds the executable in the test output directory. UsesUIA3Automationfor UI element discovery.UiTest(abstract base class,IAsyncLifetime): Provides helper methods for all UI tests:FindFirst(string automationId)-- finds UI elements by automation IDFindFirst(Func<ConditionFactory, ConditionBase>)-- finds by conditionWait(int seconds)-- async delay for UI settlingEnter(string value)-- simulates keyboard text inputPress(VirtualKeyShort)-- simulates a key press
Integration Test Runner Configuration
Tests run sequentially (no parallel test collections) with invariant culture:
{
"parallelizeTestCollections": false,
"diagnosticMessages": true,
"culture": "invariant"
}
Source: tests/Wpf.Ui.Gallery.IntegrationTests/xunit.runner.json
Run Commands
# Run unit tests
dotnet test tests/Wpf.Ui.UnitTests/Wpf.Ui.UnitTests.csproj
# Run integration tests (requires built Gallery app)
dotnet test tests/Wpf.Ui.Gallery.IntegrationTests/Wpf.Ui.Gallery.IntegrationTests.csproj
# Run all tests with coverage
dotnet test tests/Wpf.Ui.UnitTests/Wpf.Ui.UnitTests.csproj --collect:"XPlat Code Coverage"
Coverage Gaps and Observations
| Area | Current Coverage | Notes |
|---|---|---|
| Animations | 2 unit tests | TransitionAnimationProvider edge cases only |
| Extensions | 4 unit tests | SymbolExtensions.Swap() and GetString() exhaustive enum tests |
| Controls (77+) | 0 unit tests | Controls depend on WPF runtime; consider UI automation expansion |
| Services | 0 unit tests | INavigationService, IContentDialogService, etc. are testable via mocks |
| Theming | 0 tests | Static managers (ApplicationThemeManager) limit testability |
| Win32 Interop | 0 tests | Requires OS-level interaction; integration tests more appropriate |
| Window Chrome | 3 integration tests | TitleBar close/minimize/maximize buttons |
| Navigation | 2 integration tests | AutoSuggestBox search and sidebar navigation |
| Dialogs | 2 integration tests | ContentDialog result text and keyboard focus isolation |
| Window | 1 integration test | Window title verification |
CI/CD Integration
The PR validation workflow (.github/workflows/wpf-ui-pr-validator.yaml) currently only builds the Gallery app in Release mode. It does not run unit or integration tests as part of PR checks. Test execution is a local development responsibility.