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:
<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:
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:
<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:
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.