Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
직접 명령은 검토 Prompt를 거치지 않습니다. 실행하기 전에 소스를 확인하세요.
npx skills add https://github.com/tomevault-io/skills-registry --skill maui-unit-testing명령은 한 줄로 유지됩니다. 복사하기 전에 가로로 스크롤해 전체 내용을 확인하세요.
로컬 사본을 원하시나요? SkillsMP에서 현재 제공할 수 있는 파일을 다운로드하세요.
SKILL.md 표시 중
SOC 직업 분류 기준
| name | maui-unit-testing |
| description | > Use when this capability is needed. |
For project templates, xUnit examples, ViewModel test patterns, and CLI commands, see references/unit-testing-api.md.
<!-- ❌ xUnit can't run on platform-specific TFMs -->
<TargetFramework>net9.0-ios</TargetFramework>
<!-- ✅ Use plain .NET TFM for desktop test host -->
<TargetFrameworks>net9.0;net10.0</TargetFrameworks>
If your test project references the app project, the app tries to build as Exe for the test TFM — this fails. Add a conditional OutputType:
<!-- ❌ App .csproj without conditional — breaks test builds -->
<OutputType>Exe</OutputType>
<!-- ✅ Library for test TFM, Exe for platform TFMs -->
<PropertyGroup Condition="'$(TargetFramework)' == 'net9.0'">
<OutputType>Library</OutputType>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' != 'net9.0'">
<OutputType>Exe</OutputType>
</PropertyGroup>
ViewModels that directly call static MAUI APIs are untestable:
// ❌ Untestable — Shell.Current requires a running MAUI app
public async Task GoToDetail(int id)
=> await Shell.Current.GoToAsync($"detail?id={id}");
// ✅ Inject an interface — fully testable
public class MyViewModel(INavigationService nav)
{
public async Task GoToDetail(int id)
=> await nav.GoToAsync($"detail?id={id}");
}
Static APIs to wrap behind interfaces:
Shell.Current → INavigationServiceApplication.Current → avoid entirelySecureStorage.Default → ISecureStorageConnectivity.Current → IConnectivity// ❌ Testing the binding — fragile, needs a running UI
Assert.Equal("Hello", label.Text);
// ✅ Testing the ViewModel — fast, no platform dependency
Assert.Equal("Hello", viewModel.Title);
Assert.True(viewModel.SaveCommand.CanExecute(null));
| MAUI Service | Mock Strategy |
|---|---|
ISecureStorage | Mock<ISecureStorage> — stub GetAsync/SetAsync |
IPreferences | Mock<IPreferences> — stub Get/Set/Remove |
IConnectivity | Mock<IConnectivity> — return NetworkAccess |
IGeolocation | Mock<IGeolocation> — return fixed Location |
IFilePicker | Mock<IFilePicker> — return FileResult |
IMediaPicker | Mock<IMediaPicker> — return FileResult |
| Shell navigation | Abstract behind INavigationService |
IDispatcher | Stub Dispatch to invoke action synchronously |
Define service interfaces so ViewModels have zero MAUI platform dependencies:
// ✅ These make your entire ViewModel layer testable
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.
Application.Current or Shell.Current in ViewModels — wrap in injectable servicesObservableCollection<T> and [ObservableProperty] (MVVM Toolkit) for testable stateCanExecute, not UI bindingsTaskCompletionSource to test async waiting flowsdotnet test in CI to catch regressions earlyxunit.runner.devices for real platform APIs (sensors, camera, Bluetooth)net9.0/net10.0 (not platform-specific TFMs)OutputType for test TFMIDispatcher mocked to invoke synchronously in testsdotnet test runs green in CIConverted and distributed by TomeVault — claim your Tome and manage your conversions.