Extends Verify to allow verification of Avalonia UIs.
Entity Framework Extensions is a major sponsor and is proud to contribute to the development this project.
The test project needs a ModuleInitializer
and an Avalonia application with a Style
.
[assembly: AvaloniaTestApplication(typeof(VerifyAvaloniaSetupApplication))]
public class VerifyAvaloniaSetupApplication : Application
{
[ModuleInitializer]
public static void Init()
{
VerifyImageMagick.RegisterComparers(.24);
VerifierSettings.InitializePlugins();
}
public static AppBuilder BuildAvaloniaApp() =>
AppBuilder
.Configure<VerifyAvaloniaSetupApplication>()
.UseSkia()
.UseHeadless(
new()
{
UseHeadlessDrawing = false
});
public VerifyAvaloniaSetupApplication() =>
Styles.Add(new FluentTheme());
}
And add the following NuGet packages:
- https://nuget.org/packages/Verify.Avalonia/
- https://nuget.org/packages/Avalonia.Headless.XUnit/
- https://nuget.org/packages/Avalonia.Themes.Fluent/
- https://nuget.org/packages/Avalonia.Skia/
See Milestones for release notes.
Leverages Avalonia Headless Testing.
See Headless Testing with XUnit and Headless Testing with NUnit for more information.
[ModuleInitializer]
public static void Init()
{
VerifyImageMagick.RegisterComparers(0.17);
VerifyAvalonia.Initialize();
}
This sample uses Verify.ImageMagick to ignore small rendering differences that are expected between different operating systems.
Other comparers options:
- https://github.com/VerifyTests/Verify.ImageHash
- https://github.com/VerifyTests/Verify.ImageMagick
- https://github.com/VerifyTests/Verify.Phash
- https://github.com/VerifyTests/Verify.ImageSharp.Compare
Many Avalonia projects use CommunityToolkit.Mvvm. To ensure proper serialization of MVVM commands, use Verify.CommunityToolkit.Mvvm.
Ensure tests projects have InternalsVisibleTo configured in the target app so tests can use generated controls by name.
<ItemGroup>
<InternalsVisibleTo Include="NUnitTests" />
<InternalsVisibleTo Include="XUnitTests" />
</ItemGroup>
The [AvaloniaTestApplication]
attribute wires the tests in the current project with the specific application. It needs to be defined once per project in any file. Verify.Avalonia requires that UseHeadlessDrawing
is disabled and .UseSkia()
is set.
[assembly: AvaloniaTestApplication(typeof(TestAppBuilder))]
public static class TestAppBuilder
{
public static AppBuilder BuildAvaloniaApp() =>
AppBuilder.Configure<App>()
.UseSkia()
.UseHeadless(
new()
{
UseHeadlessDrawing = false
});
}
public class CalculatorTests
{
[AvaloniaFact]
public Task Should_Add_Numbers()
{
var window = new MainWindow
{
DataContext = new MainWindowViewModel()
};
window.Show();
// Set values to the input boxes
window.FirstOperandInput.Text = "10";
window.SecondOperandInput.Text = "20";
// Raise click event on the button:
window.AddButton.Focus();
window.KeyPressQwerty(PhysicalKey.Enter, RawInputModifiers.None);
Assert.Equal("30", window.ResultBox.Text);
return Verify(window);
}
}
Should_Add_Numbers.verified.verified.png:
{
Type: MainWindow,
SizeToContent: WidthAndHeight,
Title: Simple Calculator,
CanResize: false,
Content: {
Type: StackPanel,
Spacing: 10.0,
Width: 280.0,
Height: 175.0,
Margin: 10,
HorizontalAlignment: Left,
Children: [
{
Type: TextBox,
Text: 10,
Watermark: Operand 1,
Name: FirstOperandInput
},
{
Type: TextBox,
Text: 20,
Watermark: Operand 2,
Name: SecondOperandInput
},
{
Type: UniformGrid,
Columns: 4,
Children: [
{
Type: Button,
Command: MainWindowViewModel.Add,
Content: +,
Name: AddButton
},
{
Type: Button,
Command: MainWindowViewModel.Subtract,
Content: -,
Name: SubtractButton
},
{
Type: Button,
Command: MainWindowViewModel.Multiply,
Content: *,
Name: MultiplyButton
},
{
Type: Button,
Command: MainWindowViewModel.Divide,
Content: /,
Name: DivideButton
}
]
},
{
Type: StackPanel,
Spacing: 10.0,
Orientation: Horizontal,
Children: [
{
Type: TextBlock,
Text: Result:
},
{
Type: TextBlock,
Text: 30,
Name: ResultBox
}
]
}
]
},
Background: LightGray,
Width: 300.0,
Height: 195.0,
IsVisible: true,
DataContext: {
FirstOperand: 10.0,
SecondOperand: 20.0,
Result: 30,
AddCommand: MainWindowViewModel.Add,
SubtractCommand: MainWindowViewModel.Subtract,
MultiplyCommand: MainWindowViewModel.Multiply,
DivideCommand: MainWindowViewModel.Divide
}
}
Given the control:
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="200"
Height="100"
Background="LightGray"
x:Class="TestableApp.MyUserControl">
<StackPanel Spacing="10" Margin="10" HorizontalAlignment="Left" Orientation="Vertical">
<TextBlock Text="Welcome to Avalonia!" />
<Button Content="Button" />
</StackPanel>
</UserControl>
And the test:
public class MyUserControlTests
{
[AvaloniaFact]
public Task Render()
{
var control = new MyUserControl();
return Verify(control
8000
);
}
}
Should_Add_Numbers.verified.verified.png:
{
Type: MyUserControl,
Content: {
Type: StackPanel,
Spacing: 10.0,
Orientation: Vertical,
Margin: 10,
HorizontalAlignment: Left,
Children: [
{
Type: TextBlock,
Text: Welcome to Avalonia!
},
{
Type: Button,
Content: Button
}
]
},
Background: LightGray,
Width: 200.0,
Height: 100.0
}
The VerifyAvalonia.IncludeThemeVariant()
method is used to include both ThemeVariant.Light
and ThemeVariant.Dark
variants when rendering UIs.
[ModuleInitializer]
public static void Init()
{
VerifyAvalonia.IncludeThemeVariant();
VerifierSettings.InitializePlugins();
}
Then given the same CalculatorTests
will produce the verified.txt
as above, but will now produce two pngs, the first for ThemeVariant.Light
and the second for ThemeVariant.Dark
:
The VerifyAvalonia.AddAvaloniaConvertersForAssembly
method is used to include converters for additional control assemblies.
[ModuleInitializer]
public static void Init()
{
// FluentAvalonia
VerifyAvalonia.AddAvaloniaConvertersForAssemblyOfType<NavigationView>();
VerifyImageMagick.RegisterComparers(0.17);
VerifierSettings.InitializePlugins();
}