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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +2 -2
- package/CHANGELOG.md +18 -0
- package/README.md +32 -7
- package/agents/android-architect.md +14 -70
- package/agents/android-performance-engineer.md +14 -73
- package/agents/android-test-engineer.md +23 -76
- package/agents/flutter-architect.md +17 -73
- package/agents/flutter-performance-engineer.md +14 -83
- package/agents/flutter-test-engineer.md +18 -82
- package/agents/ios-architect.md +14 -74
- package/agents/ios-performance-engineer.md +14 -73
- package/agents/ios-test-engineer.md +18 -75
- package/agents/lead-mobile.md +40 -70
- package/agents/mobile-accessibility-auditor.md +13 -45
- package/agents/mobile-code-reviewer.md +19 -48
- package/agents/mobile-release-engineer.md +15 -40
- package/agents/mobile-staff-engineer.md +18 -65
- package/agents/plugin-native-expert.md +14 -78
- package/agents/rn-architect.md +13 -76
- package/agents/rn-performance-engineer.md +14 -74
- package/agents/rn-test-engineer.md +16 -77
- package/bin/reis-mobile.mjs +30 -5
- package/commands/reis-mobile.md +7 -2
- package/commands/test.md +71 -0
- package/core/install/claude-plugin.mjs +9 -2
- package/docs/agent-context.md +57 -0
- package/package.json +2 -1
- package/skills/mobile-test/SKILL.md +21 -6
- package/skills/mobile-test/references/android.md +67 -55
- package/skills/mobile-test/references/ios.md +76 -53
|
@@ -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.
|
|
25
|
+
## 2. Bind the request to the project
|
|
26
26
|
|
|
27
|
-
|
|
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** —
|
|
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
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
5
|
+
## Resolve the module and test environment
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
12
|
+
From the directory containing the wrapper, discover available tasks before choosing one:
|
|
12
13
|
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
```bash
|
|
15
|
+
./gradlew projects
|
|
16
|
+
./gradlew '<module-path>:tasks' --all
|
|
17
|
+
adb devices -l
|
|
18
|
+
```
|
|
18
19
|
|
|
19
|
-
|
|
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
|
-
|
|
22
|
+
## Select the smallest test that proves the behavior
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
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
|
-
- `
|
|
40
|
-
-
|
|
41
|
-
-
|
|
42
|
-
-
|
|
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
|
-
##
|
|
39
|
+
## Espresso: action, synchronization and assertion
|
|
45
40
|
|
|
46
|
-
|
|
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
|
-
|
|
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
|
-
|
|
55
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
##
|
|
69
|
+
## Compose, Hilt and storage
|
|
65
70
|
|
|
66
|
-
-
|
|
67
|
-
- Hilt:
|
|
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
|
-
##
|
|
75
|
+
## Run and inspect evidence
|
|
70
76
|
|
|
71
|
-
|
|
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
|
-
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
54
|
+
## Swift Testing and dependency isolation
|
|
39
55
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
60
|
+
## XCUITest: launch state, interaction, observable result
|
|
47
61
|
|
|
48
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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.
|