askill
maui-unit-testing

maui-unit-testingSafety 100Repository

xUnit testing guidance for .NET MAUI apps — ViewModel testing, mocking MAUI services, test project setup, code coverage, and on-device test runners.

62 stars
1.2k downloads
Updated 3/3/2026

Package Files

Loading files...
SKILL.md

.NET MAUI Unit Testing with xUnit

Test Project Setup

Create a class library targeting the same TFM as your MAUI app:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFrameworks>net9.0;net10.0</TargetFrameworks>
    <IsPackable>false</IsPackable>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="xunit" Version="2.*" />
    <PackageReference Include="xunit.runner.visualstudio" Version="2.*" />
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.*" />
    <PackageReference Include="coverlet.collector" Version="6.*" />
    <PackageReference Include="Moq" Version="4.*" />
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\MyApp\MyApp.csproj" />
  </ItemGroup>
</Project>

Use net9.0 or net10.0 (not a platform-specific TFM like net9.0-ios) so xUnit can run on the desktop test host.

Conditional OutputType for App Projects

If your test project references an app project directly, prevent the app from building as an executable for the test TFM:

<!-- In the MAUI app .csproj -->
<PropertyGroup Condition="'$(TargetFramework)' == 'net9.0'">
  <OutputType>Library</OutputType>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' != 'net9.0'">
  <OutputType>Exe</OutputType>
</PropertyGroup>

xUnit Fundamentals

[Fact] — Single Test Case

public class CalculatorTests
{
    [Fact]
    public void Add_TwoNumbers_ReturnsSum()
    {
        // Arrange
        var calculator = new Calculator();

        // Act
        var result = calculator.Add(2, 3);

        // Assert
        Assert.Equal(5, result);
    }
}

[Theory] / [InlineData] — Parameterised Tests

public class ConverterTests
{
    [Theory]
    [InlineData(0, 32)]
    [InlineData(100, 212)]
    [InlineData(-40, -40)]
    public void CelsiusToFahrenheit_ReturnsExpected(double celsius, double expected)
    {
        var result = TemperatureConverter.CelsiusToFahrenheit(celsius);
        Assert.Equal(expected, result, precision: 2);
    }
}

Interface-First Architecture

Define service interfaces so ViewModels can be tested without MAUI platform dependencies:

public interface INavigationService
{
    Task GoToAsync(string route);
    Task GoBackAsync();
}

public interface IDialogService
{
    Task<bool> ConfirmAsync(string title, string message);
}

Register implementations in MauiProgram.cs; inject interfaces into ViewModels.

ViewModel Testing Pattern

public class ItemsViewModelTests
{
    private readonly Mock<IItemService> _itemServiceMock = new();
    private readonly Mock<INavigationService> _navMock = new();

    private ItemsViewModel CreateSut() =>
        new(_itemServiceMock.Object, _navMock.Object);

    [Fact]
    public async Task LoadItems_PopulatesCollection()
    {
        // Arrange
        var items = new List<Item> { new("A"), new("B") };
        _itemServiceMock.Setup(s => s.GetAllAsync()).ReturnsAsync(items);
        var sut = CreateSut();

        // Act
        await sut.LoadItemsCommand.ExecuteAsync(null);

        // Assert
        Assert.Equal(2, sut.Items.Count);
        Assert.False(sut.IsBusy);
    }

    [Fact]
    public async Task SelectItem_NavigatesToDetail()
    {
        // Arrange
        var sut = CreateSut();
        var item = new Item("Test");

        // Act
        await sut.SelectItemCommand.ExecuteAsync(item);

        // Assert
        _navMock.Verify(n => n.GoToAsync($"detail?id={item.Id}"), Times.Once);
    }
}

Mocking Common MAUI Services

MAUI ServiceMock Strategy
ISecureStorageMock<ISecureStorage> — stub GetAsync/SetAsync
IPreferencesMock<IPreferences> — stub Get/Set/Remove
IConnectivityMock<IConnectivity> — return NetworkAccess
IGeolocationMock<IGeolocation> — return fixed Location
IFilePickerMock<IFilePicker> — return FileResult
IMediaPickerMock<IMediaPicker> — return FileResult
Shell navigationAbstract behind INavigationService
IDispatcherStub Dispatch to invoke action synchronously

Running Tests

# Run all tests
dotnet test

# Run with verbosity
dotnet test --verbosity normal

# Filter by class or method
dotnet test --filter "FullyQualifiedName~ItemsViewModelTests"

# Code coverage with coverlet (outputs to TestResults/)
dotnet test --collect:"XPlat Code Coverage"

# Generate HTML coverage report (requires reportgenerator tool)
dotnet tool install -g dotnet-reportgenerator-globaltool
reportgenerator -reports:TestResults/**/coverage.cobertura.xml \
  -targetdir:TestResults/CoverageReport -reporttypes:Html

On-Device Testing

For tests requiring real platform APIs (sensors, camera, Bluetooth), use the xunit.runner.devices package to run xUnit tests inside a MAUI app on a simulator or physical device. This is separate from dotnet test and runs within the app process.

Tips

  • Keep ViewModels free of Application.Current, Shell.Current — wrap in injectable services.
  • Use ObservableCollection<T> and [ObservableProperty] (MVVM Toolkit) for testable state.
  • Assert on ViewModel properties and CanExecute, not UI bindings.
  • Use TaskCompletionSource to test async waiting flows.
  • Run dotnet test in CI to catch regressions early.

Install

Download ZIP
Requires askill CLI v1.0+

AI Quality Score

91/100Analyzed 2/19/2026

High-quality technical skill for .NET MAUI unit testing with xUnit. Provides comprehensive coverage of test project setup, xUnit fundamentals ([Fact]/[Theory]), interface-first architecture patterns, ViewModel testing with Moq, mocking strategies for MAUI services (ISecureStorage, IPreferences, IConnectivity, etc.), running tests with coverage, and on-device testing. Well-structured with excellent code examples, helpful tables, and actionable CLI commands. Includes practical tips for testable MVVM patterns. Tags (ci-cd, testing) improve discoverability. Located in dedicated skills folder suggesting reusability. Strong bonus from R11 (high-density technical reference content that is accurate and well-structured). Minor deduction for domain specificity (MAUI/xUnit) affecting reusability score.

100
90
78
92
95

Metadata

Licenseunknown
Version-
Updated3/3/2026
Publisherdavidortinau

Tags

ci-cdtesting