Mohammed Chami
.NET Developer | Content Creator
Mohammed Chami
.NET Developer | Content Creator

For Avalonia UI projects, I’d recommend xUnit as your primary testing framework. Here’s why and how to set it up:
xUnit has become the de facto standard in the .NET ecosystem, especially for modern applications. Here’s why it’s perfect for your Avalonia project:
[Fact]
public void LoginUser_ShouldUpdateState()
{
// Much cleaner than NUnit's [Test] attribute
}
[Theory]
[InlineData("john", true)]
[InlineData("", false)]
public void ValidateUsername_WithDifferentInputs(string username, bool expected)
{
// Parameterized tests are elegant in xUnit
}
xUnit was built with async/await in mind from the ground up:
[Fact]
public async Task LoginAsync_ShouldCallService()
{
// Native async support - no special attributes needed
await _loginViewModel.LoginAsync();
Assert.True(_userStateService.IsUserLoggedIn);
}
In JetBrains Rider, xUnit has the best support:
Create your test project structure:
# In your solution directory
dotnet new xunit -n YourProject.Tests
dotnet add YourProject.Tests reference YourProject
dotnet sln add YourProject.Tests
Add these to your YourProject.Tests.csproj:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<!-- For mocking -->
<PackageReference Include="Moq" Version="4.20.69" />
<!-- For Avalonia UI testing -->
<PackageReference Include="Avalonia.Headless" Version="11.0.7" />
<!-- For fluent assertions (optional but recommended) -->
<PackageReference Include="FluentAssertions" Version="6.12.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\YourProject\YourProject.csproj" />
</ItemGroup>
</Project>
using Xunit;
using FluentAssertions;
using YourProject.Services;
namespace YourProject.Tests.Services;
public class UserStateServiceTests
{
private readonly UserStateService _userStateService;
public UserStateServiceTests()
{
_userStateService = new UserStateService();
}
[Fact]
public void LoginUser_ShouldUpdateAllProperties()
{
// Arrange
const string username = "testuser";
// Act
_userStateService.LoginUser(username);
// Assert
_userStateService.IsUserLoggedIn.Should().BeTrue();
_userStateService.CurrentUserName.Should().Be(username);
_userStateService.RecentActivities.Should().NotBeEmpty();
_userStateService.RecentActivities.First().Should().Contain("logged in");
}
[Theory]
[InlineData("john")]
[InlineData("admin")]
[InlineData("user123")]
public void LoginUser_WithDifferentUsernames_ShouldWork(string username)
{
// Act
_userStateService.LoginUser(username);
// Assert
_userStateService.CurrentUserName.Should().Be(username);
_userStateService.IsUserLoggedIn.Should().BeTrue();
}
[Fact]
public void LogoutUser_AfterLogin_ShouldClearState()
{
// Arrange
_userStateService.LoginUser("testuser");
// Act
_userStateService.LogoutUser();
// Assert
_userStateService.IsUserLoggedIn.Should().BeFalse();
_userStateService.CurrentUserName.Should().BeEmpty();
}
}
using Xunit;
using Moq;
using FluentAssertions;
using YourProject.ViewModels;
using YourProject.Services;
namespace YourProject.Tests.ViewModels;
public class LoginViewModelTests
{
private readonly Mock<UserStateService> _mockUserStateService;
private readonly LoginViewModel _loginViewModel;
public LoginViewModelTests()
{
_mockUserStateService = new Mock<UserStateService>();
_loginViewModel = new LoginViewModel(_mockUserStateService.Object);
}
[Fact]
public async Task LoginAsync_WithValidCredentials_ShouldCallService()
{
// Arrange
_loginViewModel.Username = "testuser";
_loginViewModel.Password = "demo123";
// Act
await _loginViewModel.LoginCommand.ExecuteAsync(null);
// Assert
_mockUserStateService.Verify(x => x.LoginUser("testuser"), Times.Once);
}
[Theory]
[InlineData("", "demo123")]
[InlineData("user", "")]
[InlineData("", "")]
public async Task LoginAsync_WithInvalidCredentials_ShouldNotCallService(
string username, string password)
{
// Arrange
_loginViewModel.Username = username;
_loginViewModel.Password = password;
// Act
await _loginViewModel.LoginCommand.ExecuteAsync(null);
// Assert
_mockUserStateService.Verify(x => x.LoginUser(It.IsAny<string>()), Times.Never);
}
}
[Fact]
public void Username_WhenChanged_ShouldRaisePropertyChanged()
{
// Arrange
var propertyChangedRaised = false;
_loginViewModel.PropertyChanged += (_, e) =>
{
if (e.PropertyName == nameof(LoginViewModel.Username))
propertyChangedRaised = true;
};
// Act
_loginViewModel.Username = "newuser";
// Assert
propertyChangedRaised.Should().BeTrue();
}
The beauty of xUnit on Linux is how seamless it is:
# Run all tests
dotnet test
# Run with verbose output
dotnet test --logger "console;verbosity=detailed"
# Run specific test class
dotnet test --filter "UserStateServiceTests"
# Run tests with coverage (install coverlet.collector first)
dotnet test --collect:"XPlat Code Coverage"
# Watch mode for continuous testing during development
dotnet watch test
NUnit is still solid but feels dated:
MSTest is Microsoft’s framework but:
[Collection("Database collection")]
public class UserServiceIntegrationTests
{
// Tests that need to run sequentially
}
[FactOnLinux]
public void LinuxSpecificFeature_ShouldWork()
{
// Only runs on Linux - perfect for EndeavourOS!
}
public class FactOnLinuxAttribute : FactAttribute
{
public FactOnLinuxAttribute()
{
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
Skip = "Only runs on Linux";
}
}
}
Go with xUnit. It’s the modern choice that will serve you well as your Avalonia applications grow in complexity. The learning curve is gentle, the documentation is excellent.
Start with the basic setup I showed above, and gradually add more sophisticated testing patterns as you become comfortable with the framework. Your future self will thank you for the comprehensive test coverage!