Skip to content
NLNuvyntra Labs

Documentation

Testing

net10.0 ViewModels, FakeNavigator, FakeDialogs, LeakProbe, and the sample test map.

Testing package

Plugin.Maui.MVVMExpress.Testing is net10.0 and depends on Core. Put ViewModels in a shared net10.0 project so they run without MAUI. FakeNavigator is InMemoryNavigator — assert Current, Stack, and CanGoBack. LeakProbe.Track returns a WeakReference; IsCollected takes that reference, not a Func. Drop the strong reference before the assert (the leak snippet uses the first-screen HomeViewModel). Also: FakeMainThread, FakeConnectivity, FakeMessageHub, AppearAsync / DisappearAsync, and ScopedNavigator for page-scope push/pop GC. Button + popped-page collection is a Core test scenario (weak CanExecuteChanged), not a LeakProbe Button API.

public sealed class ProductListViewModel : PageViewModel
{
    public ProductListViewModel(INavigator navigator)
        : base(navigator)
    {
    }

    public Task OpenDetailsAsync(int id, CancellationToken cancellationToken) =>
        Navigator!.NavigateToAsync<ProductDetailsViewModel, ProductDetailsArgs>(
            new ProductDetailsArgs(id), cancellationToken);
}

[Fact]
public async Task OpenDetails_pushes_details()
{
    var navigator = new FakeNavigator()
        .Map<ProductDetailsViewModel>("details");
    var vm = new ProductListViewModel(navigator);

    await vm.AppearAsync();
    await vm.OpenDetailsAsync(42);

    Assert.Equal(typeof(ProductDetailsViewModel), navigator.Current);
    Assert.True(navigator.CanGoBack);
}
[Fact]
public void Dispose_makes_the_viewmodel_collectable()
{
    Assert.True(LeakProbe.IsCollected(CreateAndDispose()));
}

static WeakReference CreateAndDispose()
{
    var vm = new HomeViewModel();
    var leak = LeakProbe.Track(vm);
    vm.Dispose();
    return leak;
}

Sample map

There is no separate MVVMExpress.SampleApp repository. Samples ship in the product repo. The 15-minute path is Playground (command, navigation, dialog, form, auth, list). First-run login is AuthApp: sign in → home, plus register and forgot password (demo@mvvmexpress.dev / secret). AuthApp uses UseMvvmExpress(o => o.UseShell().UseDialogs().UseAuth<AuthLoginViewModel>()), ResetAsync replace-root, [RequiresAuth], and FormViewModel dirty confirm. The flyout catalog still lives in Plugin.Maui.MVVMExpress.Sample.

Clone and run: samples/Playground

In-repo sample map: samples/

Standalone reference app: WhatsApp clone using MVVMExpress Framework

SampleViewModelsWhat it integrates
BasicCounterViewModelViewModel, SetProperty, NotifyDependsOn, ModelCommand
CRUDProductList / ProductEditFormViewModel dirty / undo / redo, AsyncState, IValidator
NavigationHome / ProductDetails / ScopedCatalogPageViewModel, INavigator, IAcceptNavArgs<T>, IAcceptNavQuery, INotifier toast, IViewModelScopeFactory
Page stackPageStack / PageStackItemIPageNavigator, URI query, Stack / CanGoBack / PopToRoot / Replace / Reset
Auth (flyout)Login / SecureHomeIAuthState, GuardedNavigator — push secure; adapt SecureSession
PlaygroundHome / Details / Edit / Login15-minute path: UseAuth, command, nav, dialog, form, list
AuthAppAuthLogin / AuthHome / Register / ForgotUseAuth<AuthLoginViewModel>, ResetAsync replace-root, MustMatch, dirty confirm
OfflineOfflineCatalogViewModelICachedFetcher + FetchPolicy — adapt ApiCache / OfflineSync
PaginationPagedProductViewModelDelegatePagedCollection load-more + refresh (not a live inbox)
Chat hostChatHost / ChatInboxSectionHostViewModel, SnapshotCollection, SearchQuery.CommittedText, CoalescingDispatcher
WhatsApp cloneWhatsAppUICloneStandalone reference app: SectionHost, SnapshotCollection, NavigationPage
ReactiveSearchViewModelSearchQuery debounce + PropertyObservable.CombineLatest
EnterpriseEnterpriseShell / CatalogStatusChild composition, IFeatureSwitch, hub, busy, probe, auth gate
GeneratedGeneratedCatalogViewModel[Notify], [ModelCommand], [PersistState], [RegisterViewModel], [Route], [RequiresAuth]