Add test/README.md (TS-07): file naming, test/support/ and test/fixtures/, the notifier-subclass override pattern, what the source-text contract tests pin, FFmpeg, python3 and SMB requirements, running a subset, and the timing of library_browse_sqlite_paging_test. Derived from test/support/*.dart, test/fixtures/, test/source_contracts_test.dart, test/design_system_test.dart, the FFmpeg and SMB suites, test/library_browse_sqlite_paging_test.dart (timed at 25 s locally) and .github/workflows/ci.yml.
7.5 KiB
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.