<!-- mobian-agent-page publisher="dailydev" canonical="https://daily.dev/posts/snapshot-testing-on-ios-a-setup-that-actually-stays-green-wxncwpy3s" -->

---
title: Snapshot Testing on iOS: A Setup That Actually Stays Green
description: A practical, battle-tested setup for iOS snapshot testing using pointfree's swift-snapshot-testing, UIKit, and XCTest, covering fixture data design, a shared...
canonical: https://daily.dev/posts/snapshot-testing-on-ios-a-setup-that-actually-stays-green-wxncwpy3s
twitter:card: summary_large_image
twitter:site: @dailydotdev
og:type: website
og:site_name: daily.dev
og:title: Snapshot Testing on iOS: A Setup That Actually Stays Green | daily.dev
og:description: A practical, battle-tested setup for iOS snapshot testing using pointfree's swift-snapshot-testing, UIKit, and XCTest, covering fixture data design, a shared...
og:url: https://daily.dev/posts/snapshot-testing-on-ios-a-setup-that-actually-stays-green-wxncwpy3s
og:image: https://api.daily.dev/og/posts/wXncwPy3s.png
og:image:alt: Snapshot Testing on iOS: A Setup That Actually Stays Green
og:image:width: 1200
og:image:height: 630
og:locale: en
---

> ## Documentation Index
> Fetch the complete documentation index at: https://daily.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Snapshot Testing on iOS: A Setup That Actually Stays Green

**[Medium](https://daily.dev/sources/medium_js)** · 12 min read · 0 upvotes · 0 comments

## Summary

A practical, battle-tested setup for iOS snapshot testing using pointfree's swift-snapshot-testing, UIKit, and XCTest, covering fixture data design, a shared base test class handling window hosting and appearance lifecycle, pinning render size and simulator environment, explicit strict record modes, snapshotting the accessibility tree for VoiceOver coverage, recognizing and deleting flaky assertions, and turning baselines into a browsable documentation gallery. Drawn from experience running suites on production apps with up to ~1,000 baselines.

## Full article

daily.dev links to this article rather than hosting it. Read it at the original source: <https://medium.com/@tommysadiqhinrichsen/snapshot-testing-on-ios-a-setup-that-actually-stays-green-545e913f2a26>

## Questions this post answers

### Why do my UIKit collection view cells appear empty in snapshot tests even though the data loads fine in the app?

A debounced publisher firing during the run-loop pump can apply a diffable data source snapshot that marks the collection view layout valid before the final layoutIfNeeded call runs, so the section provider never re-queries the populated data. The fix is walking the view hierarchy and calling invalidateLayout() on every UICollectionView right before the final layout pass, forcing it to re-query the now-populated data source.

_Developers debugging flaky iOS snapshot suites can find setup patterns like this through daily.dev._

### Why doesn't RunLoop.main.run(until:) reliably fire Combine's receive(on: RunLoop.main) blocks in XCTest?

Under XCTest, RunLoop.main.run(until:) does not reliably deliver Combine's receive(on: RunLoop.main) blocks; they queue but never fire. The workaround is using the mechanism XCTest itself relies on: create an XCTestExpectation, fulfill it via DispatchQueue.main.asyncAfter after the desired settle time, then wait on it with XCTWaiter, which reliably pumps the run loop.

_Teams hardening async UI tests can track these XCTest quirks and fixes on daily.dev._

### How do I stop snapshot tests from silently recording bad baselines by accident?

Route recording through an environment variable with explicit levels instead of relying on swift-snapshot-testing's default, which records a missing baseline and reports it as a failure to re-run, letting typo'd tests silently commit garbage. Use RECORD_SNAPSHOTS=1 to re-record everything after intentional design changes, RECORD_SNAPSHOTS=missing to add baselines for new screens only, and default to strict mode where a missing baseline always fails.

_Developers setting up trustworthy snapshot suites can compare configuration approaches on daily.dev._

## Similar posts on daily.dev

- [Swift Testing — Hopes Entrusted to the Future](https://daily.dev/posts/swift-testing-hopes-entrusted-to-the-future-mjeviiebe) · Medium · 0 upvotes · 0 comments
- [Testing can be fun, actually](https://daily.dev/posts/testing-can-be-fun-actually-iorwrxdme) · Lobsters · 0 upvotes · 0 comments
- [Building Testable Swiftdata Apps](https://daily.dev/posts/building-testable-swiftdata-apps-e4fum1jda) · AzamSharp · 0 upvotes · 0 comments
- [3 Tips for Effective Snapshot Testing with Roborazzi](https://daily.dev/posts/3-tips-for-effective-snapshot-testing-with-roborazzi-1lkco7jab) · Atomic Spin · 0 upvotes · 0 comments

---

Tags: [#ios](https://daily.dev/tags/ios), [#accessibility](https://daily.dev/tags/accessibility)

[View this post on daily.dev](https://daily.dev/posts/snapshot-testing-on-ios-a-setup-that-actually-stays-green-wxncwpy3s)

```json
{"@context":"https://schema.org","@graph":[{"@type":"Organization","@id":"https://daily.dev/#organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180},"sameAs":["https://twitter.com/dailydotdev","https://github.com/dailydotdev","https://www.linkedin.com/company/daily-dev-ltd"]},{"@type":"WebSite","@id":"https://daily.dev/#website","url":"https://daily.dev","name":"daily.dev","publisher":{"@id":"https://daily.dev/#organization"},"potentialAction":{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https://daily.dev/search?q={search_term_string}"},"query-input":"required name=search_term_string"}}]}
{"@context":"https://schema.org","@type":"TechArticle","headline":"Snapshot Testing on iOS: A Setup That Actually Stays Green","url":"https://daily.dev/posts/snapshot-testing-on-ios-a-setup-that-actually-stays-green-wxncwpy3s","mainEntityOfPage":{"@type":"WebPage","@id":"https://daily.dev/posts/snapshot-testing-on-ios-a-setup-that-actually-stays-green-wxncwpy3s"},"datePublished":"2026-08-23T19:18:17.423Z","dateModified":"2026-08-23T19:38:20.799Z","description":"A practical, battle-tested setup for iOS snapshot testing using pointfree's swift-snapshot-testing, UIKit, and XCTest, covering fixture data design, a shared...","image":"https://media.daily.dev/image/upload/f_auto,q_auto/v1/posts/8cee15b838657152a9d482edd3204107?_a=AQAEuop","thumbnailUrl":"https://media.daily.dev/image/upload/f_auto,q_auto/v1/posts/8cee15b838657152a9d482edd3204107?_a=AQAEuop","isAccessibleForFree":true,"articleSection":"Medium","inLanguage":"en","publisher":{"@type":"Organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180}},"author":{"@type":"Organization","name":"Medium","logo":"https://media.daily.dev/image/upload/t_logo,f_auto/v1/logos/medium","url":"https://daily.dev/sources/medium_js"},"commentCount":0,"discussionUrl":"https://daily.dev/posts/snapshot-testing-on-ios-a-setup-that-actually-stays-green-wxncwpy3s","interactionStatistic":[{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":0},{"@type":"InteractionCounter","interactionType":{"@type":"CommentAction"},"userInteractionCount":0}],"keywords":"ios,accessibility","timeRequired":"PT12M"}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://daily.dev"},{"@type":"ListItem","position":2,"name":"Medium","item":"https://daily.dev/sources/medium_js"},{"@type":"ListItem","position":3,"name":"Snapshot Testing on iOS: A Setup That Actually Stays Green"}]}
{"@context":"https://schema.org","@type":"FAQPage","@id":"https://daily.dev/posts/snapshot-testing-on-ios-a-setup-that-actually-stays-green-wxncwpy3s#faq","mainEntity":[{"@type":"Question","name":"Why do my UIKit collection view cells appear empty in snapshot tests even though the data loads fine in the app?","acceptedAnswer":{"@type":"Answer","text":"A debounced publisher firing during the run-loop pump can apply a diffable data source snapshot that marks the collection view layout valid before the final layoutIfNeeded call runs, so the section provider never re-queries the populated data. The fix is walking the view hierarchy and calling invalidateLayout() on every UICollectionView right before the final layout pass, forcing it to re-query the now-populated data source. Developers debugging flaky iOS snapshot suites can find setup patterns like this through daily.dev."}},{"@type":"Question","name":"Why doesn't RunLoop.main.run(until:) reliably fire Combine's receive(on: RunLoop.main) blocks in XCTest?","acceptedAnswer":{"@type":"Answer","text":"Under XCTest, RunLoop.main.run(until:) does not reliably deliver Combine's receive(on: RunLoop.main) blocks; they queue but never fire. The workaround is using the mechanism XCTest itself relies on: create an XCTestExpectation, fulfill it via DispatchQueue.main.asyncAfter after the desired settle time, then wait on it with XCTWaiter, which reliably pumps the run loop. Teams hardening async UI tests can track these XCTest quirks and fixes on daily.dev."}},{"@type":"Question","name":"How do I stop snapshot tests from silently recording bad baselines by accident?","acceptedAnswer":{"@type":"Answer","text":"Route recording through an environment variable with explicit levels instead of relying on swift-snapshot-testing's default, which records a missing baseline and reports it as a failure to re-run, letting typo'd tests silently commit garbage. Use RECORD_SNAPSHOTS=1 to re-record everything after intentional design changes, RECORD_SNAPSHOTS=missing to add baselines for new screens only, and default to strict mode where a missing baseline always fails. Developers setting up trustworthy snapshot suites can compare configuration approaches on daily.dev."}}]}
```

