reis-mobile 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -22,14 +22,27 @@ Detect the stack first (the router result, or `pubspec.yaml`, Gradle files, the
22
22
 
23
23
  A cross-platform app with native code (`android/`, `ios/`, native modules) uses the native reference for that code.
24
24
 
25
- ## 2. Follow the project before this skill
25
+ ## 2. Bind the request to the project
26
26
 
27
- Read the existing tests, test dependencies, helpers and folder layout before writing anything. A project that already uses mockito, XCTest or Detox gets tests in that style unless the user asks to migrate; consistency beats preference.
27
+ Use the [shared project brief](../../docs/agent-context.md). Reuse a supplied brief and fill only missing evidence; do not repeat discovery performed by the calling command or agent. Read the relevant implementation, nearest tests, fixtures, dependency injection and test configuration before proposing a test.
28
+
29
+ Infer the operation from the request:
30
+
31
+ | Operation | Work |
32
+ |---|---|
33
+ | Run | Execute the requested existing suite or the smallest relevant suite; report failures without silently changing application behavior. |
34
+ | Write | Add tests for the named behavior using the current framework and fixtures. |
35
+ | Fix | Reproduce the failing test, distinguish a product defect from a test defect, and preserve the intended assertion while fixing the cause. |
36
+ | Audit | Inspect existing tests and give evidence-backed gaps; do not add dependencies or rewrite suites as part of a review. |
37
+
38
+ For each selected behavior, record an actual **source path and symbol → input/trigger → observable result → dependency seam → test path → execution command**. Name the repository, service, state owner or native bridge involved instead of returning a generic testing checklist. An unknown product rule is a gap to surface, not a fixture to invent.
39
+
40
+ A project that already uses mockito, XCTest or Detox gets tests in that style unless the user asks to migrate. Use its existing build variant, test target and helper conventions. Examples in references illustrate a technique; their application symbols and command placeholders must be replaced with repository evidence.
28
41
 
29
42
  ## 3. What to test, in order of value
30
43
 
31
44
  1. **Business rules** — use cases, view models, reducers, mappers — with their edge cases: empty data, missing fields, boundary values.
32
- 2. **State changes** — the exact sequence of states on success and on failure.
45
+ 2. **State changes** — observable states and transitions on success and failure; require an exact sequence only when the contract guarantees every emission. A conflating state holder may skip intermediate values.
33
46
  3. **Error handling** — a failing data source becomes the expected user-facing state.
34
47
  4. **Critical UI states** — loading, error, empty, content, and what the user can do in each.
35
48
  5. **End-to-end flows** — only the few journeys that must never break (sign-in, purchase), because they are slow and fragile.
@@ -44,7 +57,7 @@ Most tests belong at the top of this list. If a behavior can be verified without
44
57
 
45
58
  ## 5. Determinism
46
59
 
47
- - No real network, disk, clock, randomness or sleeps. Inject a clock and dispatchers or schedulers; use the platform's virtual time or fake async.
60
+ - Isolate network, time and randomness in unit tests. Integration tests may use a temporary database or filesystem when that is the behavior under test; create and clean it per test. Avoid production services and arbitrary sleeps. Inject a clock and dispatchers or schedulers; use the platform's virtual time or fake async.
48
61
  - Each test sets up its own state; order must not matter.
49
62
  - A flaky test is a bug. Find the race (unawaited async work, animations, shared state) instead of adding retries or longer timeouts.
50
63
 
@@ -56,8 +69,10 @@ Most tests belong at the top of this list. If a behavior can be verified without
56
69
 
57
70
  ## 7. Run before reporting
58
71
 
59
- Run the tests you wrote with the command in the platform reference and fix until they pass. Never weaken an assertion or delete a test to make the suite green. Instrumented, UI and end-to-end tests need a device, emulator or simulator: ask before starting one.
72
+ Resolve the command from the project's scripts, Gradle tasks, Xcode scheme/test plan or CI before execution; do not run an example with guessed names. Run the relevant existing test first when reproducing a failure, then run the changed tests and their affected suite. Never weaken an assertion or delete a test to make the suite green.
73
+
74
+ For instrumented, UI and end-to-end tests, discover available devices and the project's runner policy. Use a compatible local emulator or simulator when authorized by the task; do not erase its data or silently switch to a physical device. If the SDK, runtime or destination is unavailable, report the exact blocker and the resolved command that remains unrun. A build, a skipped test or an empty filtered run is not a passing test: inspect the test count and results.
60
75
 
61
76
  ## Output
62
77
 
63
- When writing tests, report the files created, what each test covers and the command that ran them. When auditing, list the files reviewed, fragile or flaky tests with the reason, and untested scenarios prioritized by risk, each with a concrete suggestion.
78
+ Report the operation, source symbols and behaviors covered, files changed, exact command and working directory, executed test counts/result and any report artifact. Separate passed, failed, skipped and blocked checks. When auditing, link each gap or flaky test to a concrete path, the missing assertion or race, and the smallest useful test to add.
@@ -1,83 +1,95 @@
1
1
  # Testing native Android apps
2
2
 
3
- For setting up a test stack from scratch (dependencies, UI tests, screenshot tests, Hilt), follow [android/setup/GUIDE.md](android/setup/GUIDE.md). This file covers writing tests in an existing setup.
3
+ Use this reference for Kotlin/Java tests, including native Android modules inside Flutter or React Native apps. For a new test setup, use [android/setup/GUIDE.md](android/setup/GUIDE.md); first inspect the current module rather than installing its entire example stack.
4
4
 
5
- ## Detect the setup
5
+ ## Resolve the module and test environment
6
6
 
7
- - Test dependencies in the module build files and `gradle/libs.versions.toml`: JUnit 4 or 5, `kotlinx-coroutines-test`, Turbine, MockK or Mockito-Kotlin, Truth or AssertJ, Robolectric, `androidx.compose.ui:ui-test-junit4`, Espresso, `hilt-android-testing`.
8
- - Source sets: **local tests** in `src/test` run on the JVM; **instrumented tests** in `src/androidTest` need a device or emulator.
9
- - Existing helpers: a `MainDispatcherRule`, fakes, a `:core:testing` module, custom test runners.
7
+ 1. Read `settings.gradle` or `settings.gradle.kts` to find the owning module. Inspect its build file, convention plugins, version catalog and CI invocation for the active build type and product flavors.
8
+ 2. Locate the subject and its nearest test. Identify the real constructor, injected repository/dispatcher, observable state, test fixture and source set. `src/test` normally holds JVM tests; `src/androidTest` holds device tests. Check variant-specific and custom source sets before choosing a destination file.
9
+ 3. Reuse the installed JUnit engine, assertion library, coroutine test helpers and mocking tool. Do not switch a JUnit 4 suite to JUnit 5 because an example uses another runner.
10
+ 4. For instrumentation, inspect `testInstrumentationRunner`, runner arguments, AndroidX test dependencies and any Hilt runner. `AndroidJUnitRunner` is the standard runner; preserve a configured custom subclass. Espresso needs the module's `androidTestImplementation` setup, including `espresso-core` and the test runner. Resolve versions from the project instead of copying documentation versions. See [Espresso setup](https://developer.android.com/training/testing/espresso/setup).
10
11
 
11
- ## Local unit tests: ViewModels, coroutines and Flow
12
+ From the directory containing the wrapper, discover available tasks before choosing one:
12
13
 
13
- ```kotlin
14
- class MainDispatcherRule(val dispatcher: TestDispatcher = UnconfinedTestDispatcher()) : TestWatcher() {
15
- override fun starting(description: Description) = Dispatchers.setMain(dispatcher)
16
- override fun finished(description: Description) = Dispatchers.resetMain()
17
- }
14
+ ```bash
15
+ ./gradlew projects
16
+ ./gradlew '<module-path>:tasks' --all
17
+ adb devices -l
18
+ ```
18
19
 
19
- class LoginViewModelTest {
20
- @get:Rule val mainDispatcherRule = MainDispatcherRule()
20
+ Replace `<module-path>` with the discovered Gradle path, including its leading colon. The task listing determines the unit, connected or managed-device task for the requested variant. Do not assume `:app`, `Debug` or a connected device exists.
21
21
 
22
- private val repository = FakeAuthRepository()
22
+ ## Select the smallest test that proves the behavior
23
23
 
24
- @Test
25
- fun `emits success when credentials are valid`() = runTest {
26
- repository.nextResult = Result.success(fakeUser)
27
- val viewModel = LoginViewModel(repository)
24
+ | Subject | Test and concrete observation |
25
+ |---|---|
26
+ | Mapper, validator, use case | JVM test with inputs and expected domain result, including a meaningful boundary or failure case. |
27
+ | ViewModel or Flow producer | JVM coroutine test with the project's dispatcher rule; trigger the action and observe the state or emitted event. |
28
+ | Stateless composable | Existing Compose test harness; interact with a semantic node and assert the resulting state or callback effect. |
29
+ | Android Views screen | Espresso instrumentation test against real view IDs and a controlled dependency seam. |
30
+ | DAO, worker or persistent store | Existing Room/WorkManager/DataStore harness; assert stored values, constraints or work outcome and clean resources. |
28
31
 
29
- viewModel.uiState.test { // Turbine
30
- assertEquals(LoginUiState.Idle, awaitItem())
31
- viewModel.submit("user@example.com", "secret")
32
- assertEquals(LoginUiState.Loading, awaitItem())
33
- assertEquals(LoginUiState.Success(fakeUser), awaitItem())
34
- }
35
- }
36
- }
37
- ```
32
+ ## Coroutines and Flow
38
33
 
39
- - `runTest` skips `delay` with virtual time; `advanceUntilIdle()` and `advanceTimeBy()` control it with a `StandardTestDispatcher`.
40
- - Inject dispatchers into repositories and use cases instead of hardcoding `Dispatchers.IO`; tests pass the test dispatcher.
41
- - Code that uses `viewModelScope` needs `Dispatchers.Main` replaced (the rule above).
42
- - The names above are illustrative; use the project's classes.
34
+ - Reuse the project's `MainDispatcherRule` or equivalent when the subject uses `viewModelScope`. Share the test scheduler across injected dispatchers; hardcoded `Dispatchers.IO` work is not controlled by virtual time.
35
+ - Use `runTest` and the project's Flow helper, such as Turbine. Start collection before the action when the contract requires observing emissions; launch long-lived collectors in a scope cancelled at test completion.
36
+ - A `StateFlow` can conflate rapid updates. Assert its stable final state unless intermediate states are part of the contract; to test loading, pause a fake dependency until the assertion has observed that state, then complete it. Do not assume a synchronous fake guarantees `Idle → Loading → Success` emissions.
37
+ - With `StandardTestDispatcher`, explicitly drive scheduled work where necessary using `runCurrent`, `advanceTimeBy` or `advanceUntilIdle`. Do not replace a race with a real delay. See [testing Kotlin coroutines](https://developer.android.com/kotlin/coroutines/test) and [testing flows](https://developer.android.com/kotlin/flow/test).
43
38
 
44
- ## Compose UI tests
39
+ ## Espresso: action, synchronization and assertion
45
40
 
46
- ```kotlin
47
- class LoginScreenTest {
48
- @get:Rule val composeRule = createComposeRule()
41
+ Bind the test to a real Activity/Fragment, view IDs, launch state and expected result. Reuse an existing fake backend or dependency override before launch. Use the app's actual validation rule or response; checking that a button exists does not prove submitting the form works.
49
42
 
50
- @Test
51
- fun showsErrorWhenLoginFails() {
52
- composeRule.setContent { LoginScreen(state = LoginUiState.Error("Invalid credentials"), onSubmit = {}) }
43
+ This illustrative body assumes the inspected app rejects an empty form and exposes these IDs/resources. Replace them with the actual screen's symbols and use the project's test imports and runner:
53
44
 
54
- composeRule.onNodeWithText("Invalid credentials").assertIsDisplayed()
55
- composeRule.onNodeWithText("Sign in").performClick()
45
+ ```kotlin
46
+ @Test
47
+ fun submittingEmptyFormShowsRequiredEmailError() {
48
+ ActivityScenario.launch(SignInActivity::class.java).use {
49
+ onView(withId(R.id.email)).perform(clearText(), closeSoftKeyboard())
50
+ onView(withId(R.id.submit)).perform(click())
51
+ onView(withId(R.id.email_error))
52
+ .check(matches(withText(R.string.email_required)))
53
+ .check(matches(isDisplayed()))
56
54
  }
57
55
  }
58
56
  ```
59
57
 
60
- - Test stateless composables with state passed in; they need no ViewModel or DI.
61
- - Prefer finding nodes by text or content description; add `Modifier.testTag` only where there is no user-visible handle.
62
- - These tests are instrumented by default; with Robolectric configured, they can run as local tests.
58
+ `ActivityScenario` is `androidx.test.core.app.ActivityScenario`; the other calls come from Espresso's `Espresso`, `ViewActions`, `ViewMatchers` and `ViewAssertions`. Match resource imports to the owning module. `use` closes the activity after the assertion, including on failure. See [testing activities](https://developer.android.com/guide/components/activities/testing).
59
+
60
+ For asynchronous work outside Espresso's built-in synchronization:
61
+
62
+ 1. Identify the operation that makes the assertion race. Use the app's existing `IdlingResource`, or connect one to that operation's actual lifecycle. An unconnected counter cannot synchronize anything.
63
+ 2. Register it through `IdlingRegistry.getInstance().register(resource)` before the first Espresso interaction that needs it. If startup work is under test, arrange the dependency and registration before launching the activity; an auto-launch rule can run before `@Before`.
64
+ 3. With `CountingIdlingResource`, increment before scheduling work and decrement on every completion path, including errors and cancellation. Finish relevant state updates before marking the operation idle.
65
+ 4. Unregister through `IdlingRegistry.getInstance().unregister(resource)` in `@After` or guaranteed cleanup. Restore dependency overrides as well. Avoid `Thread.sleep`, unbounded waits and increasing timeouts to hide a race.
66
+
67
+ Keep idling resources free of Activity/View references. See [Espresso idling resources](https://developer.android.com/training/testing/espresso/idling-resource).
63
68
 
64
- ## Views, Espresso and Hilt
69
+ ## Compose, Hilt and storage
65
70
 
66
- - Espresso: `onView(withId(R.id.submit)).perform(click())`, `check(matches(isDisplayed()))`. Disable animations on test devices, and use idling resources for background work instead of sleeps.
67
- - Hilt: annotate the test with `@HiltAndroidTest`, add `HiltAndroidRule`, use a custom runner that sets `HiltTestApplication`, and replace modules with `@TestInstallIn`. Details in [android/setup/references/android/training/dependency-injection/hilt-testing.md](android/setup/references/android/training/dependency-injection/hilt-testing.md).
71
+ - Compose: use the installed `createComposeRule` or `createAndroidComposeRule` harness. Assert after an interaction, including a callback value when testing a stateless composable. Prefer semantics such as text/content description, using a test tag when a node otherwise cannot be identified. Run through the project's configured instrumentation or Robolectric setup.
72
+ - Hilt: preserve `@HiltAndroidTest`, `HiltAndroidRule`, custom runner and module replacement conventions; install fakes before the subject is created. See [the Hilt test guide](android/setup/references/android/training/dependency-injection/hilt-testing.md).
73
+ - Room: use an isolated database for DAO behavior and `MigrationTestHelper` for migrations. WorkManager: use the existing `work-testing` harness. DataStore: use a temporary location and cancel its scope during teardown. Do not replace a persistence test with a mock of the persistence operation.
68
74
 
69
- ## Other components
75
+ ## Run and inspect evidence
70
76
 
71
- - **Room**: test DAOs against `Room.inMemoryDatabaseBuilder`; test migrations with `MigrationTestHelper` in instrumented tests.
72
- - **WorkManager**: `work-testing` provides test drivers to run workers synchronously.
73
- - **DataStore**: create it in a temporary folder per test.
77
+ The following commands are templates. Replace task paths and class names with values found above; use `--tests` only for a Gradle JVM test task:
74
78
 
75
- ## Commands
79
+ ```bash
80
+ ./gradlew '<unit-test-task-path>' --tests '<fully-qualified-test-class>'
81
+ ./gradlew '<instrumentation-test-task-path>'
82
+ ```
83
+
84
+ For one instrumentation class or method, reuse the project's runner-argument convention. Alternatively, after building and installing the matching app/test APKs, inspect the installed instrumentation component and select it explicitly:
76
85
 
77
86
  ```bash
78
- ./gradlew :app:testDebugUnitTest # local tests of one module
79
- ./gradlew :app:testDebugUnitTest --tests '*LoginViewModelTest'
80
- ./gradlew :app:connectedDebugAndroidTest # instrumented tests, needs a device or emulator
87
+ adb -s '<serial>' shell pm list instrumentation
88
+ adb -s '<serial>' shell am instrument -w \
89
+ -e class '<fully-qualified-test-class>#<test-method>' \
90
+ '<test-package>/<runner-class>'
81
91
  ```
82
92
 
83
- For coverage, enable `enableUnitTestCoverage` / `enableAndroidTestCoverage` on the build type and run the coverage report task the Android Gradle plugin creates for it; check the task name for the project's AGP version.
93
+ Do not infer the test package from the app namespace. Read the runner output and test counts: no matching tests is not success. Gradle local reports live under the module's `build/reports/tests` and XML results under `build/test-results`; connected-test output is under `build/reports/androidTests/connected` and `build/outputs/androidTest-results/connected`. Report the actual generated path and selected device. See [command-line testing](https://developer.android.com/studio/test/command-line).
94
+
95
+ For coverage, inspect the module's AGP version and existing configuration, then discover the generated report task. Do not add a coverage plugin or change toolchain versions just to execute an existing test suite.
@@ -1,79 +1,102 @@
1
1
  # Testing native iOS apps
2
2
 
3
- ## Detect the setup
3
+ Use this reference for Swift/Objective-C tests and native iOS modules inside Flutter or React Native apps. Keep XCTest, Swift Testing and XCUITest in the roles already established by the target.
4
4
 
5
- - Framework: **Swift Testing** (`import Testing`, `@Test`, `#expect`) or **XCTest** (`XCTestCase`). Swift Testing needs Xcode 16 or later; both can live in the same test target, so follow what the file or target already uses.
6
- - Test targets, test plans and schemes in the Xcode project, or test targets in `Package.swift`.
7
- - Whether unit tests run inside a host app (the app target launches) or as a logic test bundle.
8
- - Existing doubles and fixtures; snapshot testing only if the project already has a library for it.
5
+ ## Resolve the container, target and destination
9
6
 
10
- ## Unit tests with Swift Testing
7
+ 1. Find the owning `.xcworkspace`, `.xcodeproj` or `Package.swift`, and read CI's test invocation. Use the app's workspace when that is how its dependencies are integrated; do not select a dependency's internal project/workspace.
8
+ 2. Inspect the scheme's Test action and `.xctestplan` files. Identify the unit/UI test target, test host, source membership, exclusions and configurations. A new file outside the test target or a target excluded by the plan contributes no tests.
9
+ 3. Inspect `import XCTest` / `XCTestCase` versus `import Testing` / `@Test`, the toolchain version and existing helpers. Preserve the framework used by the nearest tests. Swift Testing is available with Xcode 16 and later; it can coexist with XCTest in a test target. See [adding tests to Xcode projects](https://developer.apple.com/documentation/xcode/adding-tests-to-your-xcode-project).
10
+ 4. Map the real source symbol to its protocol dependencies, actor isolation, input and observable result. Read the existing fixture builders rather than inventing application entities.
11
11
 
12
- ```swift
13
- import Testing
14
- @testable import App
12
+ These are command templates: replace every `<...>` from repository or tool output. Use `-project '<path>.xcodeproj'` instead of `-workspace '<path>.xcworkspace'` throughout when the project has no relevant workspace.
15
13
 
16
- @MainActor
17
- struct LoginViewModelTests {
18
- @Test func emitsSuccessWhenCredentialsAreValid() async {
19
- let viewModel = LoginViewModel(authService: FakeAuthService(result: .success(.fixture)))
14
+ ```bash
15
+ xcodebuild -list -workspace '<workspace-path>.xcworkspace'
16
+ xcodebuild -workspace '<workspace-path>.xcworkspace' -scheme '<scheme>' -showTestPlans
17
+ xcodebuild -workspace '<workspace-path>.xcworkspace' -scheme '<scheme>' -showdestinations
18
+ xcrun simctl list devices available
19
+ ```
20
20
 
21
- await viewModel.submit(email: "user@example.com", password: "secret")
21
+ Choose a compatible destination listed for that scheme, using its simulator ID to avoid ambiguous device names. A listed simulator may still need to boot. Preserve the project's destination policy and avoid selecting a physical device implicitly. If there is no compatible runtime, record the blocker. See [Xcode command-line discovery](https://developer.apple.com/library/archive/technotes/tn2339/_index.html) and [test plans](https://developer.apple.com/documentation/xcode/organizing-tests-to-improve-feedback).
22
22
 
23
- #expect(viewModel.state == .success(.fixture))
24
- }
23
+ ## XCTest: choose the async mechanism that matches the subject
24
+
25
+ - Synchronous function: invoke it and assert its returned value or observable state with `XCTAssert...`; use `try XCTUnwrap` for values required by subsequent steps.
26
+ - `async` function: use a `test...` method marked `async` or `async throws`, await the operation, then assert. Apply `@MainActor` when the subject requires it; do not add an unawaited `Task` inside a synchronous test.
27
+ - Callback, delegate or publisher: create an expectation before triggering work, assert the callback's result, fulfill it, and wait for completion. For an async XCTest method use `await fulfillment(of:timeout:)` when supported by the project's toolchain; a synchronous method can use `wait(for:timeout:)`. Avoid a blocking wait inside an async test.
28
+ - If the method only starts background work, awaiting that method alone may not prove completion. Observe the completion callback/state transition rather than immediately asserting a value that races.
25
29
 
26
- @Test(arguments: ["", "no-at-sign", "a@"])
27
- func rejectsInvalidEmails(_ email: String) {
28
- #expect(EmailValidator.isValid(email) == false)
30
+ See [asynchronous XCTest tests](https://developer.apple.com/documentation/xctest/asynchronous-tests-and-expectations) and [async expectation fulfillment](https://developer.apple.com/documentation/xctest/xctestcase/fulfillment%28of%3Atimeout%3Aenforceorder%3A%29).
31
+
32
+ The following method illustrates a callback test. `repository`, `cachedOrder` and `loadCachedOrder` stand for an inspected project fixture and API, not generated application types. Arrange an isolated repository fixture that contains the expected order before invoking it:
33
+
34
+ ```swift
35
+ func testLoadsPreviouslySavedOrder() async {
36
+ let loaded = expectation(description: "Cached order is delivered")
37
+
38
+ repository.loadCachedOrder { result in
39
+ switch result {
40
+ case .success(let order):
41
+ XCTAssertEqual(order, cachedOrder)
42
+ case .failure(let error):
43
+ XCTFail("Expected the saved order, received \(error)")
44
+ }
45
+ loaded.fulfill()
29
46
  }
47
+
48
+ await fulfillment(of: [loaded], timeout: 1.0)
30
49
  }
31
50
  ```
32
51
 
33
- - `#expect` records a failure and continues; `try #require(...)` stops the test (use it to unwrap optionals the rest of the test depends on).
34
- - `#expect(throws: SomeError.self) { try parser.parse(data) }` checks thrown errors.
35
- - Parameterized tests (`arguments:`) replace copy-pasted test methods.
36
- - Suites are plain types; setup goes in `init`, teardown in `deinit`.
52
+ Choose a bounded timeout appropriate to the isolated operation. Keep captured values compatible with the API's actor/Sendable requirements. Fulfill on both success and failure so assertion failures do not become unexplained timeouts; clean subscriptions and tasks during teardown.
37
53
 
38
- ## Unit tests with XCTest
54
+ ## Swift Testing and dependency isolation
39
55
 
40
- ```swift
41
- final class LoginViewModelTests: XCTestCase {
42
- @MainActor
43
- func testEmitsSuccessWhenCredentialsAreValid() async {
44
- let viewModel = LoginViewModel(authService: FakeAuthService(result: .success(.fixture)))
56
+ - Follow the target's current `@Test`, `#expect` and `#require` conventions. Parameterize genuinely identical behavior with different inputs instead of duplicating bodies. Preserve the suite's isolation and parallelization assumptions; do not migrate XCTest UI tests to Swift Testing.
57
+ - Inject the app's existing protocol-based services, clock and persistence boundary. When testing URL loading, a custom `URLProtocol` belongs on an isolated `URLSessionConfiguration`, with a deterministic handler scoped to that test.
58
+ - Use a dedicated `UserDefaults` suite or temporary persistence store with cleanup. Keep real Keychain/network access out of unit tests unless those integrations are explicitly the subject and have an isolated test setup. Reset fixtures in setup/teardown; avoid global state shared by parallel tests.
45
59
 
46
- await viewModel.submit(email: "user@example.com", password: "secret")
60
+ ## XCUITest: launch state, interaction, observable result
47
61
 
48
- XCTAssertEqual(viewModel.state, .success(.fixture))
49
- }
50
- }
51
- ```
62
+ Use the UI test target with `XCTestCase` and `XCUIApplication`. It runs separately from the app, so an in-process fake in the test does not replace the app's services. Reuse launch arguments/environment that the app actually reads to select a deterministic fixture; do not invent flags such as `--uitesting` and assume they work.
52
63
 
53
- - Use `async` test methods and `await` the work instead of `XCTestExpectation` with timeouts where possible.
54
- - Methods must start with `test`; `setUp`/`tearDown` (or their `async throws` variants) reset state.
64
+ Locate elements through identifiers found in SwiftUI's `.accessibilityIdentifier(...)` or UIKit's `accessibilityIdentifier`. Wait for the relevant state using `waitForExistence(timeout:)` rather than sleeping. Element existence alone does not guarantee it is enabled or hittable; inspect those states when the action requires them. See [XCUIElement](https://developer.apple.com/documentation/xcuiautomation/xcuielement).
55
65
 
56
- ## Test doubles and determinism
66
+ Illustrative method: replace the identifiers and expected error with the inspected screen's values. This scenario assumes an empty form is submitted and the app shows a required-field error:
57
67
 
58
- - Inject dependencies through protocols in initializers; fakes return canned results and record calls when the test needs them.
59
- - No real network: fake the API client, or register a custom `URLProtocol` on the test `URLSessionConfiguration`.
60
- - No shared `UserDefaults.standard` or real Keychain: inject a store, or use `UserDefaults(suiteName:)` removed after each test.
61
- - Inject a clock or date provider instead of reading `Date()` in code under test.
68
+ ```swift
69
+ func testSubmittingEmptyFormShowsRequiredEmailError() {
70
+ let app = XCUIApplication()
71
+ app.launch()
72
+ defer { app.terminate() }
73
+
74
+ let submit = app.buttons["signIn.submit"]
75
+ XCTAssertTrue(submit.waitForExistence(timeout: 5))
76
+ XCTAssertTrue(submit.isEnabled)
77
+ XCTAssertTrue(submit.isHittable)
78
+ submit.tap()
79
+
80
+ let error = app.staticTexts["signIn.emailError"]
81
+ XCTAssertTrue(error.waitForExistence(timeout: 5))
82
+ XCTAssertEqual(error.label, "Email is required")
83
+ }
84
+ ```
62
85
 
63
- ## UI tests (XCUITest)
86
+ Set the app's existing fixture arguments/environment before `launch()` when needed to reach a fresh form. The post-tap assertion proves the behavior; a launch-only smoke test does not. For network-driven flows, make the app consume a controlled response and assert the resulting screen/state.
64
87
 
65
- - Find elements by accessibility identifier: set `.accessibilityIdentifier("loginButton")` in SwiftUI or `accessibilityIdentifier` in UIKit.
66
- - Launch into a known state with `app.launchArguments` / `app.launchEnvironment` read by the app (for example to use fake services or skip onboarding).
67
- - Wait with `waitForExistence(timeout:)` on the element, never with `sleep`.
68
- - Keep UI tests to critical flows; they are slow and the first to go flaky.
88
+ ## Run the selected tests and inspect the result
69
89
 
70
- ## Commands
90
+ Use actual names found above. The `-only-testing` identifier uses the test target name, then the XCTest class and optionally method. Select a plan only when the scheme has that plan; omit `-testPlan` for schemes without one:
71
91
 
72
92
  ```bash
73
- xcrun simctl list devices available # pick a simulator that exists on this machine
74
- xcodebuild test -scheme App -destination 'platform=iOS Simulator,name=<Device>'
75
- xcodebuild test -scheme App -destination 'platform=iOS Simulator,name=<Device>' -only-testing:AppTests/LoginViewModelTests
76
- xcodebuild test -scheme App -destination '...' -enableCodeCoverage YES -resultBundlePath TestResults.xcresult
77
- xcrun xccov view --report TestResults.xcresult
78
- swift test # Swift packages
93
+ xcodebuild test -workspace '<workspace-path>.xcworkspace' \
94
+ -scheme '<scheme>' -testPlan '<test-plan>' \
95
+ -destination 'platform=iOS Simulator,id=<simulator-id>' \
96
+ '-only-testing:<test-target>/<test-class>' \
97
+ -resultBundlePath '<new-result-path>.xcresult'
79
98
  ```
99
+
100
+ Use a new result-bundle path per run, then inspect executed tests, failures and skips. A successful build or zero matching tests is not a passing suite. Preserve the `.xcresult` path in the report. For coverage, add `-enableCodeCoverage YES` when requested and inspect the bundle with `xcrun xccov view --report '<result-path>.xcresult'`; verify command support with the installed Xcode. See [running tests and interpreting results](https://developer.apple.com/documentation/xcode/running-tests-and-interpreting-results).
101
+
102
+ For a Swift package whose tests support the host platform, run `swift test` from the package directory, using its supported filter when narrowing scope. iOS-only package tests still need the app/Xcode scheme and compatible simulator; `swift test` on macOS does not supply an iOS runtime.