Use the error container surface, leading icon, card radius and spacing shared by ErrorCard, align the card with the Home search bar on tablets, and open the full report in the scrollable app sheet instead of an inline ExpansionTile nested inside the Home scroll view.
Unit and Widget Tests
Host tests for Dart code. They run with flutter test and in CI. Device
tests live in integration_test/ (see integration_test/README.md).
Layout
Test files sit directly in test/; only shared code has subfolders.
| Path | Contents |
|---|---|
test/*_test.dart |
All test suites |
test/support/ |
Shared fakes, fixtures and frozen "legacy" baselines, imported by unit and device tests |
test/fixtures/ |
Data files: JSON contract fixtures shared with Rust tests, and a TLS certificate for the WebDAV and artwork proxy tests |
Naming
- When a suite tests one module, name it after that file:
lib/services/album_completeness.dartis tested bytest/album_completeness_test.dart. Roughly half of the suites follow this. - Otherwise prefix by feature area and describe the behavior, for example
download_queue_persistence_test.dart,library_partial_scan_test.dart,mornye_bottom_bar_test.dart,network_storage_service_test.dart. The common prefixes aredownload_,library_,mornye_,network_,track_,native_andextension_. - Put a fake used by more than one suite in
test/support/, not in a copy per file.
Shared Support
| File | Use |
|---|---|
test/support/fixed_settings.dart |
FixedSettingsNotifier: serves fixed AppSettings without reading preferences |
test/support/sqlite_process_database.dart |
SqliteProcessDatabase: a sqflite Database backed by real SQLite through a python3 child process |
test/support/app_icon_finder.dart |
findAppIcon: finds an icon drawn by Icon or AppIcon |
test/support/library_paging_fixture.dart, test/support/library_native_fixture.dart |
Library database fixtures |
test/support/re_enrich_settings.dart |
ReEnrichTestSettings: default settings that skip the lyrics sync to the backend |
test/support/virtual_time.dart |
VirtualTime: a timer zone that skips idle time while keeping real microtasks |
test/support/music_player_automix_harness.dart |
Shared AutoMix player fakes used by the music_player_*_test.dart suites |
*_benchmark.dart, *_baseline.dart in test/support/ |
Frozen pre-optimization implementations, compared against production code in unit and device tests |
Shared Contract Fixtures
Some fixtures are read by Dart and Rust tests, so both sides parse the same
data: native_library_contract.json, native_metadata_parsers.json,
native_lyrics_parsers.json, native_backup_contract.json and
collection_restore_contract.json in test/fixtures/. Change them only
together with both parsers.
Patterns
Notifier Subclasses
Many tests replace a Riverpod notifier with a small subclass that overrides
build() or one method, then install it with overrideWith:
class _History extends DownloadHistoryNotifier {
@override
DownloadHistoryState build() => DownloadHistoryState();
}
final container = ProviderContainer(
overrides: [
settingsProvider.overrideWith(() => FixedSettingsNotifier()),
downloadHistoryProvider.overrideWith(_History.new),
],
);
This is used for DownloadQueueNotifier, DownloadHistoryNotifier,
LocalLibraryNotifier, LibraryCollectionsNotifier, ExtensionNotifier
and SettingsNotifier. Keep the public methods these tests override on the
notifier class itself when refactoring a provider; moving them to a helper
breaks the subclasses.
Source Contract Tests
Several suites read production source text on purpose. Two of them exist only for that:
test/source_contracts_test.dartpins text across Dart, Kotlin and Swift:lib/services/app_state_database.dart: database version 4 and its playback-session migration.ios/Runner/AppDelegate.swiftandlib/providers/download_queue_provider.dart: the iOS background download task is acquired before work starts, and expiry cancels downloads before notifying Dart, which pauses and requeues.NativeDownloadFinalizer.kt,NativeFinalizerHistory.kt,NativeHistoryStore.ktandDownloadServiceSnapshot.kt(inandroid/app/src/main/kotlin/com/zarz/spotiflac/), withlib/services/history_schema.dartandlib/providers/download_queue_provider_native_worker.dart: the native worker contract version, the history schema version, columns and indexes, and the single progress-delta wait.android/app/src/test/resources/finalization_quality_cases.tsv: quality labels shared with the Kotlin tests.
test/design_system_test.dartscans every.dartfile underlib/:- every
IconButtonhas a tooltip; - only
app_sliver_header.dartusesexpandedTitleScale; - only
app_bottom_sheet.dartdraws a drag handle, and onlyapp_theme.dartsets a modal sheet shape; - only
selection_bottom_bar.dartandapp_menu_button.dartcreate anOverlayEntry; album_screen.dart,playlist_screen.dart,local_album_screen.dart,downloaded_album_screen.dartandlibrary_tracks_folder_screen.dartuseCollectionScaffold(, andplaylist_screen.dartusesSelectionModeMixin<PlaylistScreen>;lib/screens/now_playing_screen.dartusesshowAppBottomSheetand noPopupMenuButton.
- every
Other suites read a production file alongside their behavior tests:
sqlite_helpers_test.dart (lib/services/sqlite_helpers.dart),
downloaded_embedded_cover_resolver_test.dart
(lib/screens/track_metadata_screen_cover.dart and
track_metadata_cards.dart), queue_library_refresh_policy_test.dart
(lib/screens/queue_tab_collection_items.dart), and the schema tests
library_queue_store_test.dart, album_completeness_test.dart,
album_completeness_query_test.dart and replaygain_library_filter_test.dart
(CREATE TABLE text from lib/services/history_schema.dart and
library_schema.dart). Find them with rg -n "File\('(lib|android|ios)/" test.
When a refactor moves pinned code, update the path or the assertion in the same commit. Never delete these checks.
Host Tools
| Tool | Needed by | Without it |
|---|---|---|
ffmpeg on PATH |
ffmpeg_decryption_output_test.dart, automix_effect_renderer_test.dart |
The FFmpeg cases are skipped (markTestSkipped) |
ffmpeg and ffprobe on PATH |
ffmpeg_opus_metadata_test.dart |
The round-trip cases are skipped |
python3 |
Suites using SqliteProcessDatabase |
Those suites fail |
SPOTIFLAC_TEST_FFMPEG selects another FFmpeg binary for
automix_effect_renderer_test.dart. The CI workflow
(.github/workflows/ci.yml) installs ffmpeg before running the tests, so
these cases are not skipped there.
Optional SMB cases are skipped unless their environment is set:
TEST_SMB=1 (with scripts/smb2_fixture_server.py) and TEST_SMB_GUEST_PORT
in network_storage_service_test.dart; TEST_SMB_LIBRARY in
network_smb_transport_test.dart; TEST_SMB_UPLOAD=1 in
network_smb_upload_test.dart. TEST_SMB_PORT overrides the fixture port.
Running
flutter test # everything
flutter test test/album_completeness_test.dart # one file
flutter test test/download_queue_persistence_test.dart --plain-name 'restores'
flutter test test/source_contracts_test.dart test/design_system_test.dart
--plain-name filters by a substring of the test name.
test/library_browse_sqlite_paging_test.dart pages 1000 albums through real
SQLite. Alone it took about 25 seconds on a development Mac, close to the
default 30-second per-test timeout, so it can time out when the machine is
busy (for example during a full parallel run). Re-run it alone before treating
a failure as real.