The Kladde’s app runs on your computer and in the emulator. Between that state and an app in someone else’s hands lie two things: proof that it does what it should, and a package that someone can install. This lesson covers both, in that order, and it ends with two packages on a test device: a signed Android package and an MSIX package for Windows. You don’t upload anything to a store in this course.

As before: adjust the names to your projects. The app is called Kladde.App here, the shared library Kladde.Shared.

What each test checks

An app has more places where something can go wrong than a web application: the device, the platform, the WebView, the permissions. No test covers everything. The following table sorts the kinds you know and the ones that are new:

Test Runs Checks Doesn’t check
Unit test of the view models (lesson W2.2.1) on your computer, without a device rules, commands, state how something looks, real devices
Component test with bUnit (lesson 2.7.6, lesson W2.3.1) on your computer the markup of a Razor component, events the real WebView, real services
Device test in a test app on a device or emulator code that needs platform calls how the user interface works together
UI test with Appium against the fully installed app operation: tapping, typing, switching pages individual rules, that’s what the unit tests are for
Hands-on check on devices on a real device everything a person notices repeatability

Unit tests: the limits

Microsoft recommends xUnit as the test framework for .NET MAUI apps and describes how to make code testable. There are two ways. In the first, the code you test lives in a class library that the app and the test project both use. In the second, you add another target framework to the app that doesn’t name a platform, such as net10.0, and use a condition to make sure the app produces no executable for that target:

XML
<TargetFrameworks>net10.0;net10.0-android</TargetFrameworks>
<OutputType Condition="'$(TargetFramework)' != 'net10.0'">Exe</OutputType>

Microsoft’s example shows the same thing with an older net9.0; you use your project’s version. A test project without a platform doesn’t create a native MAUI context. Microsoft says explicitly: navigation, running on the user interface thread and device services are replaced by test doubles in such tests. You then don’t check how something is drawn natively, whether permissions are right, and whether the real implementations of the replaced services work.

For the Kladde, that means: you tested the view models without a device in lesson W2.2.1, and BookingView with bUnit in lesson W2.3.1. Neither says anything about whether MauiShareService opens a share sheet on a real phone. A device test or the hands-on check covers that.

Device tests

If tests need platform calls or native controls, the test runs in a test app on a device or emulator. Microsoft calls these device runners: a test app with a visible view for the results and hooks to start the tests from the command line with XHarness. For this, Microsoft Learn points to the wiki of the Test Device Runners project. We haven’t read which versions are required. This course doesn’t build one. For .NET 11, Microsoft names its own templates (androidtest and others); that’s a preview, and the course stays with .NET 10.

UI tests with Appium

Appium is an open-source tool that operates native, hybrid and web apps through platform drivers, the way a person would. Microsoft describes it for .NET MAUI:

Platform Driver Runs on
Android UIAutomator2 Windows, macOS, Linux
iOS XCUITest macOS only
Mac Catalyst Mac2 macOS only
Windows Windows Windows only

So on a Windows computer you test Android and Windows, on a Mac Android, iOS and Mac Catalyst. You prepare it like this:

PowerShell
npm install -g appium
appium driver install uiautomator2
appium driver install windows
appium

The first line needs Node.js, the third the driver for Windows; afterward the server listens on port 4723. For Windows, Microsoft also names the Windows Application Driver, in exactly version 1.2.1; for Android, ANDROID_HOME must point to the Android SDK.

Elements are found through the AutomationId, which you give to every element that a test should operate:

XML
<Button x:Name="CounterBtn"
        AutomationId="CounterBtn"
        Text="Click me"
        Clicked="OnCounterClicked" />

Microsoft’s example creates a separate test project for each platform and shares the test code in a common project. The base class looks like this there:

C#
public abstract class BaseTest
{
    protected AppiumDriver App => AppiumSetup.App;

    protected AppiumElement FindUIElement(string id)
    {
        if (App is WindowsDriver)
        {
            return App.FindElement(MobileBy.AccessibilityId(id));
        }

        return App.FindElement(MobileBy.Id(id));
    }
}

One test project per platform is run, for example with dotnet test UITests.Android/UITests.Android.csproj, after the app is installed on the device or in the emulator. Microsoft names two special points for Android: in debug builds, NoReset must be set to true, because otherwise Appium deletes the files of fast deployment, and the MainActivity needs a [Register] attribute with the package name. Microsoft’s example uses NUnit; your unit tests stay with xUnit.

Open for this course: We haven’t read how Appium finds elements in the WebView of a BlazorWebView; the AutomationId applies to native controls. For the Kladde, that means: with Appium you test the shell, the XAML pages and the opening of the booking view. The view itself is checked by bUnit.