docs/architecture/TESTING-SPEC.md
This document provides testing guidelines for AI agents working on the WPF UI project. The project uses separate unit and integration test suites with distinct frameworks and patterns.
Project: tests/Wpf.Ui.UnitTests/
Target Framework: net10.0-windows
Project Reference: Wpf.Ui
Project: tests/Wpf.Ui.Gallery.IntegrationTests/
Target Framework: net10.0-windows10.0.26100.0
Project References: Wpf.Ui.FlaUI, Wpf.Ui.Gallery
xunit, xunit.runner.visualstudio)xunit.v3, xunit.runner.visualstudio)MethodName_ExpectedResult_WhenCondition
Alternative format:
GivenCondition_MethodName_ExpectedResult
using Xunit;
using NSubstitute;
using Wpf.Ui.Animations; // Namespace under test
namespace Wpf.Ui.UnitTests.Animations;
public class TransitionAnimationProviderTests
{
[Fact]
public void ApplyTransition_ReturnsFalse_WhenDurationIsLessThan10()
{
// Arrange
UIElement mockedUiElement = Substitute.For<UIElement>();
// Act
var result = TransitionAnimationProvider.ApplyTransition(
mockedUiElement,
Transition.FadeIn,
-10
);
// Assert
Assert.False(result);
}
[Fact]
public void ApplyTransition_ReturnsFalse_WhenElementIsNull()
{
// Arrange
UIElement? nullElement = null;
// Act
var result = TransitionAnimationProvider.ApplyTransition(
nullElement,
Transition.FadeIn,
100
);
// Assert
Assert.False(result);
}
}
// Create mock
UIElement element = Substitute.For<UIElement>();
// Setup return value
INavigationService service = Substitute.For<INavigationService>();
service.Navigate(typeof(DashboardPage)).Returns(true);
// Verify call
service.Received(1).Navigate(Arg.Any<Type>());
Located in tests/Wpf.Ui.UnitTests/Usings.cs:
global using System;
global using System.Windows;
global using NSubstitute;
global using Xunit;
Subject_ShouldExpectedBehavior_WhenCondition
All integration tests inherit from UiTest:
using AwesomeAssertions;
using FlaUI.Core.AutomationElements;
using FlaUI.UIA3.Patterns;
namespace Wpf.Ui.Gallery.IntegrationTests;
public sealed class NavigationTests : UiTest
{
[Fact]
public async Task Settings_ShouldBeAvailable_ThroughAutoSuggestBox()
{
// Arrange
Wpf.Ui.FlaUI.AutoSuggestBox? autoSuggestBox =
FindFirst("NavigationAutoSuggestBox")?.As<AutoSuggestBox>();
autoSuggestBox.Should().NotBeNull(
"because AutoSuggestBox should be present in the navigation bar"
);
// Act
autoSuggestBox!.Enter("Settings");
await Wait(1);
// Assert
TextBox? pageTitle = FindFirst("PageTitle")?.AsTextBox();
pageTitle.Should().NotBeNull();
pageTitle!.Text.Should().Be("Settings");
}
}
Location: tests/Wpf.Ui.Gallery.IntegrationTests/Fixtures/UiTest.cs
Provided Methods:
// Find element by automation ID
protected AutomationElement? FindFirst(string automationId)
// Find element by condition
protected AutomationElement? FindFirst(Func<ConditionFactory, ConditionBase> buildCondition)
// Wait for specified seconds
protected async Task Wait(int seconds, CancellationToken cancellationToken = default)
// Type text
protected void Enter(string text)
// Press key
protected void Press(VirtualKeyShort key)
Lifecycle:
IAsyncLifetime implementationTestedApplication fixture// Null checks
element.Should().NotBeNull("because element must exist");
element.Should().BeNull();
// String assertions
text.Should().Be("Expected");
text.Should().Contain("substring");
text.Should().StartWith("prefix");
// Boolean assertions
condition.Should().BeTrue("because condition must be met");
Application?.HasExited.Should().BeTrue();
// Collection assertions
items.Should().HaveCount(5);
items.Should().Contain(item);
// Find and cast to specific control
Button? button = FindFirst("ButtonId").AsButton();
TextBox? textBox = FindFirst("TextBoxId")?.AsTextBox();
// Custom automation elements
var autoSuggestBox = FindFirst("AutoSuggestBoxId")?.AsAutoSuggestBox();
// Interact with controls
button.Click(moveMouse: false);
textBox.Text = "value";
// Pattern-based interaction
var invokePattern = element.Patterns.Invoke.Pattern;
invokePattern.Invoke();
# Run all unit tests
dotnet test tests/Wpf.Ui.UnitTests/
# Run specific test class
dotnet test tests/Wpf.Ui.UnitTests/ --filter "FullyQualifiedName~TransitionAnimationProviderTests"
# Run with coverage
dotnet test tests/Wpf.Ui.UnitTests/ --collect:"XPlat Code Coverage"
# Run all integration tests
dotnet test tests/Wpf.Ui.Gallery.IntegrationTests/
# Run specific test
dotnet test tests/Wpf.Ui.Gallery.IntegrationTests/ --filter "FullyQualifiedName~TitleBarTests"
# Run with diagnostics
dotnet test tests/Wpf.Ui.Gallery.IntegrationTests/ --logger "console;verbosity=detailed"
Location: tests/Wpf.Ui.Gallery.IntegrationTests/xunit.runner.json
{
"$schema": "https://xunit.net/schema/current/xunit.runner.schema.json",
"parallelizeTestCollections": false,
"diagnosticMessages": true,
"culture": "invariant"
}
Important: Integration tests do NOT run in parallel due to single Gallery app instance.
Test namespaces mirror source namespaces:
src/Wpf.Ui/Animations/TransitionAnimationProvider.cs
↓
tests/Wpf.Ui.UnitTests/Animations/TransitionAnimationProviderTests.cs
{ClassName}Tests.cs
Current Unit Test Coverage:
TransitionAnimationProviderSymbolExtensions.Swap(), SymbolExtensions.GetString()Current Integration Test Coverage:
Unit Test Template:
tests/Wpf.Ui.UnitTests/Animations/TransitionAnimationProviderTests.cs
tests/Wpf.Ui.UnitTests/Extensions/SymbolExtensionsTests.cs
Integration Test Template:
tests/Wpf.Ui.Gallery.IntegrationTests/TitleBarTests.cs
tests/Wpf.Ui.Gallery.IntegrationTests/NavigationTests.cs
[Fact]
public void PropertyName_DefaultValue_IsExpected()
{
var control = new MyControl();
Assert.Equal(expectedDefault, control.PropertyName);
}
[Fact]
public void PropertyName_CanBeSet_AndRetrieved()
{
var control = new MyControl();
var expectedValue = new SomeType();
control.PropertyName = expectedValue;
Assert.Equal(expectedValue, control.PropertyName);
}
[Fact]
public void Navigate_ReturnsTrue_WhenNavigationSucceeds()
{
// Arrange
var pageProvider = Substitute.For<INavigationViewPageProvider>();
pageProvider.GetPage(Arg.Any<Type>()).Returns(new DashboardPage());
var service = new NavigationService(pageProvider);
var navigationView = Substitute.For<INavigationView>();
service.SetNavigationControl(navigationView);
// Act
bool result = service.Navigate(typeof(DashboardPage));
// Assert
Assert.True(result);
}
Tests are NOT currently run in CI (PR validator only builds Gallery app).
To add test execution to CI, modify .github/workflows/wpf-ui-pr-validator.yaml:
- name: Run Unit Tests
run: dotnet test tests/Wpf.Ui.UnitTests/ --no-restore --verbosity normal
- name: Run Integration Tests
run: dotnet test tests/Wpf.Ui.Gallery.IntegrationTests/ --no-restore --verbosity normal