Tutorials › Launch Lab Notes › Chapter 20

What the Tests Catch

LabChapter 20 of the Launch Lab Notes22 minAugust 20, 2026Intermediate

canShip requires build ≥ 400.

A unit harness asserts both 418 and 200. A shallow UI harness only asks whether a Ship button exists. This bench runs the same gate under three scenarios and shows who blinks.

Filmstrip: HEALTHY both green, VM BUG UNIT red UI green, UI BUG label fail.

Healthy — both agree

ShipmentGate.canShip(build: 418) // true
ShipmentGate.canShip(build: 200) // false
SCENARIO HEALTHY. UNIT 2/2 all pass. UI 2/2 all pass. canShip(418) true, canShip(200) false. Ship 418 enabled, Ship 200 disabled.
HarnessScoreNotes
UNIT2/2418 true, 200 false
UI2/2button exists + a11y label

Sample UI: Ship 418 enabled, Ship 200 disabled.

VM bug — unit fails, UI stays green

Invert the gate. Build 418 wrongly refuses; build 200 wrongly ships.

SCENARIO VM BUG. UNIT 0/2. UI 2/2. canShip(418) got false want true; canShip(200) got true want false. Ship 418 disabled, Ship 200 enabled. Note: UI stayed green while UNIT failed.
HarnessScoreNotes
UNIT0/2both assertions fail
UI2/2still finds Ship + label

That green UI column is the control: existence checks do not see inverted logic. Sample UI shows the lie — Ship 200 lit, Ship 418 dark.

XCUITest agreed with the shallow harness

Reusing the ch8 LaunchLabAXTests target:

testShipButtonExists_vmBug_stillFindsButton passed

It launched -mode vm, waited for ship-418, and passed — same miss the on-screen UI column prints.

UI bug — unit green, label fails

Logic restored; accessibility label stripped.

SCENARIO UI BUG. UNIT 2/2. UI 1/2. a11y label FAIL unlabelled. UNIT stayed green; UI caught the missing label.
HarnessScoreNotes
UNIT2/2gate sound again
UI1/2button exists PASS, a11y label FAIL

Unit tests do not open VoiceOver's tree. A labelled control is a UI concern — ch8's lesson, reused here.

Pyramid, photographed

TDD books split unit / integration / UI. This still is the split as scoreboards: UNIT catches the inverted ViewModel; UI catches the missing label; neither catches everything alone.

Mini-exercise

Add testCanShip200_vmMode that launches -mode vm and asserts Ship 200 is not enabled. Watch it fail today — that is the missing assertion the shallow harness skipped.

Challenges

  1. Add a LaunchLabUnitTests target that imports ShipmentGate and asserts the healthy matrix without the bench UI.
  2. Extend the UI test to assert Ship 200 is not hittable in healthy mode.
  3. Inject a network client protocol and show a mock unit test vs a live UI smoke.
  4. Make the UI harness read the on-screen PASS/FAIL labels via XCUIApplication().staticTexts.
  5. Wire CI to run -only-testing:LaunchLabAXTests/ShipmentGateUITests on the LaunchLab simulator.

Key Points

Next up: dependency injection — one screen wired to a live service, a stub, and a failing stub, switchable by launch argument.

SwiftUI
SwiftUI tutorials for building native app screens, layouts, navigation, and state-driven interfaces.
Swift
Swift fundamentals for app developers who want to understand the language behind real iOS and macOS apps.
Ship iOS
Shipping workflows for iOS apps.
📚 Go deeper with LIPAI WANG’s hands-on Udemy bootcampsBrowse all courses →
← Ch 19: What the Subviews AnsweredCh 21: Swap the Service→
SwiftUIUltimate SwiftUI SeriesSwiftUI tutorials for building native app screens, layouts, navigation, and state-driven interfaces.SwiftUltimate Swift SeriesSwift fundamentals for app developers who want to understand the language behind real iOS and macOS apps.Ship iOSShip iOS Apps SeriesShipping workflows for iOS apps: signing, TestFlight, App Store Connect, CI, and release hygiene.

Ship your apps faster

When you're ready to publish your Swift app to the App Store, Simple App Shipper handles metadata, screenshots, TestFlight, and submissions — all in one place.

Try Simple App Shipper
5 free articles remainingSubscribe for unlimited access