Back to Wpfui

Cross-Cutting Concern: Testing

docs/architecture/cross-cutting/testing.md

4.3.06.6 KB
Original Source

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
     /________________________________\
LayerScopeFrameworkAssertion LibraryCount
Unit TestsPure logic, extension methods, animation providersXUnit 2.9.3 + NSubstitute 5.3.0Xunit.Assert6
Integration TestsWindow management, navigation, dialogs, title barXUnit v3 3.2.0 + FlaUI.UIA3 5.0.0AwesomeAssertions 9.3.08

Test Framework Versions

All versions are managed centrally in Directory.Packages.props:

PackageVersionPurpose
xunit2.9.3Unit test framework (v2-style API)
xunit.v33.2.0Integration test framework (v3 with IAsyncLifetime)
xunit.runner.visualstudio3.1.5Visual Studio / dotnet test runner
Microsoft.NET.Test.Sdk18.0.0.NET test SDK infrastructure
NSubstitute5.3.0Mocking library for unit tests
AwesomeAssertions9.3.0Fluent assertions (FluentAssertions successor)
FlaUI.Core5.0.0UI automation core library
FlaUI.UIA35.0.0UIA3 automation adapter
coverlet.collector6.0.4Code coverage collection

Test Naming Conventions

Unit Tests

Pattern: MethodName_ExpectedResult_WhenCondition

csharp
[Fact]
public void ApplyTransition_ReturnsFalse_WhenDurationIsLessThan10()

Alternative pattern: GivenX_Method_ExpectedResult

csharp
[Fact]
public void GivenAllRegularSymbols_Swap_ReturnsValidFilledSymbol()

Integration Tests

Pattern: Subject_ShouldExpectedBehavior_WhenCondition

csharp
[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

mermaid
graph TD
    A["tests/"] --> B["Wpf.Ui.UnitTests/"]
    A --> C["Wpf.Ui.Gallery.IntegrationTests/"]

    B --> B1["Wpf.Ui.UnitTests.csproj
<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
<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
<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 .exe process. Finds the executable in the test output directory. Uses UIA3Automation for UI element discovery.
  • UiTest (abstract base class, IAsyncLifetime): Provides helper methods for all UI tests:
    • FindFirst(string automationId) -- finds UI elements by automation ID
    • FindFirst(Func<ConditionFactory, ConditionBase>) -- finds by condition
    • Wait(int seconds) -- async delay for UI settling
    • Enter(string value) -- simulates keyboard text input
    • Press(VirtualKeyShort) -- simulates a key press

Integration Test Runner Configuration

Tests run sequentially (no parallel test collections) with invariant culture:

json
{
  "parallelizeTestCollections": false,
  "diagnosticMessages": true,
  "culture": "invariant"
}

Source: tests/Wpf.Ui.Gallery.IntegrationTests/xunit.runner.json

Run Commands

bash
# 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

AreaCurrent CoverageNotes
Animations2 unit testsTransitionAnimationProvider edge cases only
Extensions4 unit testsSymbolExtensions.Swap() and GetString() exhaustive enum tests
Controls (77+)0 unit testsControls depend on WPF runtime; consider UI automation expansion
Services0 unit testsINavigationService, IContentDialogService, etc. are testable via mocks
Theming0 testsStatic managers (ApplicationThemeManager) limit testability
Win32 Interop0 testsRequires OS-level interaction; integration tests more appropriate
Window Chrome3 integration testsTitleBar close/minimize/maximize buttons
Navigation2 integration testsAutoSuggestBox search and sidebar navigation
Dialogs2 integration testsContentDialog result text and keyboard focus isolation
Window1 integration testWindow 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.