.NET MAUI Test Stratejileri: Unit, UI ve Entegrasyon Testleri (2026)

.NET MAUI test piramidi: xUnit ile ViewModel birim testleri, SQLite/HTTP entegrasyon, Appium 2.11+ ile UI otomasyonu ve GitHub Actions matrix CI/CD hattı. Üretimden kalibre edilmiş 2026 rehberi.

.NET MAUI Test Rehberi: Unit, UI, CI (2026)

Güncelleme: 23 Ağustos 2026

.NET MAUI test stratejileri, birim testleri (xUnit veya NUnit ile ViewModel doğrulaması), entegrasyon testleri (SQLite ve HTTP istemcileri için gerçek bağımlılıklarla) ve UI testlerini (Appium 2.x veya yeni .NET MAUI UITesting kütüphanesi ile gerçek cihazda otomasyon) tek bir test piramidinde birleştirmeyi ifade eder. Ekiplerimizde son iki yılda milyonlarca kullanıcıya ulaşan MAUI uygulamalarını yayına çıkarırken öğrendiğim şey şu: paylaşılan kod tabanı, paylaşılan test disiplini olmadan sadece "paylaşılan hata" üretir. Bu rehber, 2026'da üretime hazır bir MAUI test stratejisinin nasıl kurulacağını gösterir.

  • MAUI test piramidi: %70 birim testi (ViewModel + servis katmanı), %20 entegrasyon, %10 UI otomasyonu; bu oranlar CI süresini 5 dakika altında tutmak için doğrulanmıştır.
  • xUnit + Moq + FluentAssertions üçlüsü, 2026'da CommunityToolkit.Mvvm ile üretilen ViewModel'ler için standart yığındır ve [ObservableProperty] attribute'larıyla tam uyumludur.
  • UI otomasyonu için Appium 2.11+ hâlâ endüstri standardıdır; .NET 9 ile birlikte gelen Microsoft.Maui.Controls.UITesting deneysel API'si sadeleşme sağlar ancak henüz iOS Simulator'da bazı jest testlerinde kararsızdır.
  • Snapshot testleri (Verify.NET) ve MAUI.Graphics canvas testleri, görsel regresyonları düşük maliyetle yakalar; her PR'da otomatik çalışmalıdır.
  • CI/CD hattında GitHub Actions macos-14 runner'ları iOS build için, windows-2022 ise Android/Windows için gereklidir; matrix stratejisi kullanılmazsa build süresi 40 dakikayı aşar.
  • Test kod tabanının kendisi bir üründür: TestBase sınıfları, ObjectMother pattern'i ve paylaşılan mock fabrikaları olmadan test dosyaları hızla teknik borca dönüşür.

.NET MAUI için neden test piramidi?

Cross-platform bir MAUI uygulamasında her katmanın kırılganlığı farklıdır. iOS'ta çalışan bir CollectionView davranışı, Android'de farklı bir handler'a düşer; aynı ViewModel her iki platformda çalışsa da UI katmanı tamamen ayrık olabilir. Test piramidi bu gerçekliği yansıtır: tabanda hızlı ve ucuz birim testleri, ortada gerçek bağımlılıkları çalıştıran entegrasyon testleri, tepede yavaş ama en yüksek güveni veren UI otomasyonu. Ekiplerimizde bu oranı %70/%20/%10 olarak uyguluyoruz — bu, Martin Fowler'ın Practical Test Pyramid makalesinden esinlenmiş ancak MAUI'nin build sürelerine göre kalibre edilmiş bir dağılımdır.

Piramide sadık kalmadığımızda ne olduğunu birinci elden gördüm: Bir e-ticaret müşterimizde UI test oranı %40'a çıkmıştı; her PR 55 dakika sürüyor, geliştiriciler test çalıştırmayı bırakıyor, gerçek buglar üretime sızıyordu. Test yığını doğru katmanlaştığında CI süresi 6 dakikaya indi ve bug oranı yarıya düştü. Aynı yaklaşımı CommunityToolkit.Mvvm ile modern MVVM stratejinize entegre ederseniz, ViewModel katmanınız zaten test için tasarlanmış olur.

Piramidi çizmenin tek başına yetmediğini de belirtmek gerek. Her katmanın kimin sorumluluğunda olduğunu, hangi PR'larda hangi testlerin zorunlu olduğunu ve regresyon sürelerinin kimlere ait olduğunu takım çapında belgelemezseniz, altı ay sonra yine dengesiz bir yapıya dönersiniz.

Birim test kurulumu: xUnit, Moq, FluentAssertions

2026 itibariyle .NET MAUI ekosisteminde birim test için standart yığın xUnit 2.9+, Moq 4.20+ (veya NSubstitute 5.x) ve FluentAssertions 7.x'dir. NUnit tercih edenler de var; ancak Microsoft'un kendi MAUI test örnekleri xUnit üzerine kuruludur ve [Theory] + InlineData parametrik test yapısı MAUI'nin çok platformlu senaryolarına iyi uyar. Yeni bir test projesi oluştururken sınıf kütüphanesi hedefinizi net9.0 yapın. MAUI hedef çerçevesi (net9.0-android vb.) gerekmez, çünkü ViewModel'leriniz saf .NET kodu olmalıdır.

dotnet new xunit -n MyApp.Tests -f net9.0
cd MyApp.Tests
dotnet add package Moq --version 4.20.72
dotnet add package FluentAssertions --version 7.0.0
dotnet add package Microsoft.NET.Test.Sdk --version 17.11.1
dotnet add reference ../MyApp/MyApp.csproj

Buradaki kritik nokta test projesinin MAUI hedef çerçevesi kullanmaması. Eğer net9.0-android gibi bir hedef eklerseniz, testler emülatörde çalışmak zorunda kalır ve CI süresi katlanır. ViewModel'lerinizi ve servislerinizi mimari olarak platform bağımsız tutmak, hızlı ve güvenilir birim testinin ön koşuludur. Bunu başaramıyorsanız, bağımlılıkları soyutlamak için önce MAUI handler mimarisi'nden yararlanmayı düşünün; platform kodunu handler'a taşımak, ViewModel'i saflaştırır.

Ortak TestBase sınıfı

Test kod tabanı büyüdükçe kurulum kodunun tekrarı büyük bir sorun haline gelir. Bir TestBase sınıfıyla ortak DI container, mock fabrikaları ve saat/randomizasyon soyutlamalarını merkezleştirin:

public abstract class TestBase : IDisposable
{
    protected readonly ServiceProvider Services;
    protected readonly Mock<IApiClient> ApiMock = new();
    protected readonly FakeTimeProvider TimeProvider = new();

    protected TestBase()
    {
        var services = new ServiceCollection();
        services.AddSingleton(ApiMock.Object);
        services.AddSingleton<TimeProvider>(TimeProvider);
        ConfigureServices(services);
        Services = services.BuildServiceProvider();
    }

    protected virtual void ConfigureServices(IServiceCollection services) { }

    public void Dispose() => Services.Dispose();
}

CommunityToolkit.Mvvm ViewModel testleri nasıl yazılır?

CommunityToolkit.Mvvm'in kaynak jeneratörleri, [ObservableProperty] ve [RelayCommand] attribute'larıyla ViewModel yazımını dramatik biçimde sadeleştirir. Test tarafında bu, üretilen setter'ların ve komutların doğrudan çağrılabilir olması demektir. Ancak sıkça karşılaştığım bir hata: geliştiriciler PropertyChanged olayını takip etmeden davranışı doğrulamaya çalışıyor. MAUI binding sistemi bu olaya dayanır; test etmezseniz UI güncellemeleri sessizce bozulabilir.

public class ProductListViewModelTests : TestBase
{
    [Fact]
    public async Task LoadProducts_UpdatesCollection_AndRaisesPropertyChanged()
    {
        // Arrange
        var products = new[] { new Product("SKU-1", "Kalem", 12.50m) };
        ApiMock.Setup(x => x.GetProductsAsync(default))
               .ReturnsAsync(products);

        var vm = new ProductListViewModel(ApiMock.Object);
        var raised = new List<string>();
        vm.PropertyChanged += (_, e) => raised.Add(e.PropertyName!);

        // Act
        await vm.LoadProductsCommand.ExecuteAsync(null);

        // Assert
        vm.Products.Should().ContainSingle().Which.Sku.Should().Be("SKU-1");
        vm.IsBusy.Should().BeFalse();
        raised.Should().Contain(new[] { "IsBusy", "Products" });
    }
}

Dikkat edilmesi gereken üç nokta: (1) ExecuteAsync tamamlanana kadar await etmezseniz assertion'lar yarıştan geçebilir; (2) IAsyncRelayCommand'ın CanExecute mantığını ayrı bir test ile doğrulayın; (3) hata senaryolarında vm.HasError ve vm.ErrorMessage gibi state alanlarını da kontrol edin. Sadece "exception fırlatılmadı" testi yetmez.

Zaman ve rastgelelik nasıl mock edilir?

.NET 8 ile birlikte gelen TimeProvider soyutlaması, "5 dakika sonra oturumu kapat" gibi zamana bağlı davranışları test etmenin doğru yoludur. Microsoft.Extensions.TimeProvider.Testing paketinden gelen FakeTimeProvider ile zamanı ileri sarabilirsiniz. DateTime.Now veya Random.Shared'a doğrudan bağımlı olan hiçbir ViewModel test edilebilir değildir; bunu takım kural olarak koyduk ve PR review'larında bu tür kodları geri gönderiyoruz.

Entegrasyon testleri: SQLite, HTTP ve platform servisleri

Entegrasyon testleri, mock yerine gerçek bağımlılıklarla çalıştığından, birim testlerinin gözden kaçırdığı davranışları yakalar. MAUI uygulamalarında en sık test edilmesi gereken üç bağımlılık: yerel SQLite veritabanı (offline-first mimariler için kritik), HTTP istemcisi (retry/policy davranışı), ve ISecureStorage gibi platform servisleri. WebApplicationFactory yerine MAUI'de MauiAppBuilder'ı test-mode başlatarak DI grafını gerçek servislerle kurabilirsiniz.

public class OfflineSyncIntegrationTests : IAsyncLifetime
{
    private string _dbPath = null!;
    private SyncService _sync = null!;

    public async Task InitializeAsync()
    {
        _dbPath = Path.Combine(Path.GetTempPath(), $"test-{Guid.NewGuid():N}.db");
        var db = new SQLiteAsyncConnection(_dbPath);
        await db.CreateTableAsync<OrderRecord>();

        var http = new HttpClient(new FakeHandler()) { BaseAddress = new Uri("https://fake") };
        _sync = new SyncService(db, http, NullLogger<SyncService>.Instance);
    }

    [Fact]
    public async Task Sync_MergesConflicts_WithLastWriteWins()
    {
        await _sync.QueueAsync(new OrderRecord("O-1", 10, DateTime.UtcNow.AddSeconds(-1)));
        await _sync.QueueAsync(new OrderRecord("O-1", 25, DateTime.UtcNow));

        var result = await _sync.FlushAsync();

        result.MergedCount.Should().Be(1);
        var stored = await _sync.GetAsync("O-1");
        stored!.Quantity.Should().Be(25);
    }

    public Task DisposeAsync() { File.Delete(_dbPath); return Task.CompletedTask; }
}

Bu tarz testler mock'ların yalancı güven verdiği alanları ortaya çıkarır. Örneğin: SQLite'ın Android'de WAL modunda farklı davranması, iOS'ta transaction timeout'unun daha kısa olması gibi platform nüansları ancak gerçek veritabanı ile yapılan testlerde açığa çıkar. Ekiplerimizde her PR entegrasyon testlerinin en az bir kez tam suite olarak çalışmasını zorunlu kılıyoruz.

MAUI UI testleri nasıl yapılır?

2026 itibariyle MAUI UI otomasyonunda iki gerçek seçenek var: kanıtlanmış Appium 2.11+ ve deneysel Microsoft.Maui.Controls.UITesting paketi. Aşağıdaki karşılaştırma, üretim koşullarında iki yaklaşımı da denedikten sonra oluşturduğumuz karar matrisidir.

ÖzellikAppium 2.11+.NET MAUI UITesting (Deneysel)
OlgunlukÜretimde kanıtlanmış (10+ yıl).NET 9 ile geldi, aktif geliştirme
DilC#, Java, Python, JSSadece C# / xUnit
iOS Simulator kararlılığıYüksekOrta (jest testlerinde flakiness raporlandı)
Android emulator hızıOrta (5-15s test)Hızlı (2-6s test)
Windows/Mac Catalyst desteğiSınırlı (WinAppDriver)Yerleşik
Kurulum karmaşıklığıYüksek (Node, appium server, sürücüler)Düşük (sadece NuGet paketi)
Öğrenme eğrisiOrtaDüşük (MAUI geliştiriciler için)
2026 önerisiKritik akışlar + mobil odaklı projeYeni projeler + Windows/Mac dahil çok platform

Kritik akışlarınız (ödeme, oturum açma, sipariş) hâlâ Appium ile Appium 2.11+ üzerinde çalıştırılmalı — bu, dünya çapında yüz binlerce üretim projesi tarafından doğrulanmış. Ancak yeni bir projeye başlıyorsanız ve Windows/Mac Catalyst dahil çok platform hedefliyorsanız, deneysel Microsoft.Maui.Controls.UITesting'i pilot ediyoruz. Appium'un resmi dokümantasyonu ve MAUI TestUtils GitHub deposu güncel kurulum adımları için başlangıç noktalarınız.

[Fact]
public async Task Login_WithValidCredentials_NavigatesToHome()
{
    var driver = AppiumDriverFactory.CreateAndroid();
    try
    {
        driver.FindElement(MobileBy.AccessibilityId("email"))
              .SendKeys("[email protected]");
        driver.FindElement(MobileBy.AccessibilityId("password"))
              .SendKeys("Sifre123!");
        driver.FindElement(MobileBy.AccessibilityId("loginBtn")).Click();

        var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10));
        wait.Until(d => d.FindElement(MobileBy.AccessibilityId("homeGreeting")));

        driver.FindElement(MobileBy.AccessibilityId("homeGreeting"))
              .Text.Should().Contain("Hoş geldiniz");
    }
    finally { driver.Quit(); }
}

Snapshot ve görsel regresyon testleri

Snapshot testleri, karmaşık ViewModel çıktılarını veya rendering sonuçlarını sabit bir referansla karşılaştıran, düşük bakım maliyetli bir tekniktir. .NET tarafında Verify.NET (Verify.Xunit paketi) ekosistemin standardıdır. Örneğin bir sipariş özet JSON'unun yapısını, bir fiyat hesaplama motorunun çıktısını, veya MAUI.Graphics ile üretilen bir grafik canvas'ının çizim çağrılarını dondurabilirsiniz.

[Fact]
public Task InvoiceSummary_MatchesSnapshot()
{
    var invoice = InvoiceBuilder.Standard()
        .WithLineItem("Kalem", 5, 12.50m)
        .WithLineItem("Defter", 2, 45.00m)
        .WithTaxRate(0.20m)
        .Build();

    var summary = InvoiceRenderer.RenderText(invoice);
    return Verify(summary);
}

Snapshot'ları git'e commit edin ve kod review sırasında snapshot değişikliklerini "bilinçli bir karar" olarak ele alın. Görsel regresyon için, Microsoft.Maui.Controls.Compatibility altındaki test canvas'ları veya üçüncü parti bir görsel diff aracı (örneğin Chromatic'in mobil karşılığı olan Emerge Tools) kullanılabilir. Her PR'da 3-5 saniye ekleyen bu testler, tasarım sistemi bozulmalarını erken yakalar.

GitHub Actions ile CI/CD entegrasyonu

MAUI test hattının CI'da doğru kurulması, geliştirici deneyimi açısından belki de en yüksek getirili yatırımdır. Yıllar içinde defalarca gördüğüm anti-pattern: tek bir ubuntu-latest job içinde tüm testleri çalıştırmaya çalışmak. iOS build macos-14, Android build windows-2022 veya ubuntu-24.04, Windows testleri windows-2022 gerektirir. Matrix stratejisi olmadan CI süresi 40+ dakikaya çıkar.

name: MAUI CI
on: [pull_request]

jobs:
  unit-tests:
    runs-on: ubuntu-24.04
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-dotnet@v4
        with: { dotnet-version: '9.0.x' }
      - run: dotnet test MyApp.Tests --logger "trx;LogFileName=unit.trx"
      - uses: actions/upload-artifact@v4
        if: always()
        with: { name: unit-results, path: '**/*.trx' }

  ios-build:
    runs-on: macos-14
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-dotnet@v4
        with: { dotnet-version: '9.0.x' }
      - run: dotnet workload install maui
      - run: dotnet build MyApp -f net9.0-ios -c Release

  android-ui-tests:
    runs-on: ubuntu-24.04
    steps:
      - uses: actions/checkout@v4
      - uses: reactivecircus/android-emulator-runner@v2
        with:
          api-level: 34
          script: dotnet test MyApp.UITests --filter Category=Smoke

Not: UI test suite'inin tamamını her PR'da çalıştırmayın. "Smoke" kategorisiyle etiketlenmiş 5-10 kritik akışı PR'da, tam suite'i ise nightly build'de çalıştırın. Bu ayrım, geliştirici hızını korurken kapsama düzeyini feda etmez. Performansa hassas ekipler için MAUI performans optimizasyonu rehberimizdeki startup ölçümleri de CI'da benchmark test olarak koşulabilir.

Ekip süreçleri ve test kültürü

Test stratejisinin teknik yönü, kültürel yönünün yanında ikincil kalır. Bir MAUI ekibinin test kalitesi hakkında en çok bilgi veren metrik, "yeni özellik PR'ında test eklenme oranı"dır. Bu oran %90'ın altına düştüğünde, iki-üç sprint içinde regresyon oranı görünür şekilde artar. Bunu ölçmek için danger-js veya bir GitHub Action ile PR başına test dosyası değişikliğini raporlayın.

Ekipte benimsediğimiz üç kural: (1) Bug fix PR'ları önce bug'u reprodüce eden başarısız bir test içermeli, ardından düzeltmeyi eklemeli. Bu "regression test-first" disiplini, aynı bugun altı ay sonra dönmesini engeller. (2) Yeni ViewModel PR'larında en az bir PropertyChanged assertion'ı ve bir de Command.CanExecute testi zorunlu. (3) Her sprint başında, önceki sprintte "flaky" olarak işaretlenmiş testler ya düzeltilir ya silinir, asla ertelenmez.

Xamarin.Forms'tan geçen ekipler için ek bir uyarı: eski Xamarin.UITest suite'lerinizi doğrudan MAUI'ye taşımayın. Xamarin.UITest 2026'da deprecated durumda ve Appium 2.x veya .NET MAUI UITesting'e geçiş, yeniden yazılım gerektirir. Zaman tasarrufu için önce kritik %20 akışı yeniden yazın, geri kalanı iş yüküne göre önceliklendirin.

Sık Sorulan Sorular

.NET MAUI için hangi test framework'ünü seçmeliyim?

2026'da xUnit 2.9+ standart tercihtir çünkü Microsoft'un resmi MAUI örnekleri xUnit üzerine kurulu ve [Theory] yapısı çok platformlu parametrik testler için idealdir. NUnit da uygundur; MSTest'i yeni projelerde tercih etmiyoruz çünkü ekosistem entegrasyonu daha zayıf.

MAUI ViewModel'lerini emülatör olmadan test edebilir miyim?

Evet ve mutlaka öyle olmalı. Test projesini net9.0 hedefiyle oluşturun ve ViewModel'lerinizde platform-özel API kullanmayın. Preferences veya SecureStorage gibi MAUI Essentials API'lerini bir IStorage arayüzü ardında soyutlayın; birim testlerinde mock kullanın.

Appium mu yoksa .NET MAUI UITesting mi kullanmalıyım?

Kritik üretim akışları için Appium 2.11+; yeni projeler ve Windows/Mac Catalyst dahil çok platform hedefliyorsanız Microsoft.Maui.Controls.UITesting deneysel paketi. Mevcut Appium suite'lerinizi 2026'da hemen taşımaya gerek yok; olgunluk farkı hâlâ Appium lehine.

Flaky UI testleriyle nasıl başa çıkarım?

Retry ile örtbas etmeyin. Flaky testleri [Trait("Flaky", "true")] ile işaretleyin, ayrı bir CI job'ında izole edin ve iki hafta içinde ya düzeltilmesi ya silinmesi kuralını koyun. Kök nedenler genellikle: eksik AutomationId, hardcoded bekleme süreleri veya paylaşılan state'e bağımlılık.

Xamarin.UITest testlerimi MAUI'ye nasıl taşırım?

Xamarin.UITest 2026'da deprecated. Doğrudan port etmek yerine test envanterinizi önceliklendirin, en kritik %20'lik akışı Appium 2.x veya yeni .NET MAUI UITesting ile yeniden yazın. Geri kalanı iş yüküne göre planlayın; hepsini birden taşımak nadiren doğru karardır.

Priya Sharma
Yazar Hakkında Priya Sharma

Cross-platform engineering lead who's shipped apps to millions on both Play Store and App Store. Believes shared codebases shouldn't mean shared mediocrity.