Logo for tanaschita.com

Preventing forgotten database migrations with automated tests in iOS

Learn how to automatically detect migration errors before shipping.

22 Feb 2026 · 7 min read

When working with persistence in iOS apps, schema changes are inevitable. For example when adding, removing or modifying properties, renaming attributes, changing relationships, or refactoring model structures.

On a clean install, the database is created with the newest schema so everything works. Existing users, however, already have a store created with an older schema. If the app cannot migrate that store, startup may fail. Ensuring that migrations are validated automatically can prevent breaking existing installations.

In this article, we'll look at a strategy to automatically detect migration errors before shipping.

Sponsorship logo
Architecture & Design Patterns for iOS
This book is a practical guide to essential architectural principles and design patterns for iOS development. It covers strategies for building maintainable apps with Swift and SwiftUI, including dependency injection, navigation, common patterns, and modularization.
LEARN MORE

Migrations across common frameworks

Different frameworks such as SwiftData, Core Data, Realm, or manual solutions like GRDB and SQLite, handle migrations slightly differently.

Some frameworks attempt to infer certain schema changes automatically and support lightweight migrations. Others always require explicit migration logic. The level of automation varies, but the risk of forgetting a required migration remains.

What does a meaningful migration test need to prove?

A migration test should answer the following question: Can the current app version open a database created by a previous version?

In manual testing, this is done by installing a published app version and then trying to update to the new app version that contains schema changes. To automate this in a test, we'll use the same principle.

Testing with a database from a previous version

The idea is the following:

  • Add a baseline database file created with an older app version to the test target.
  • Copy that file into a temporary directory during a test.
  • Attempt to open it using the current persistence stack.
  • Fail if opening throws an error.

Conceptually, a test might look like this:

import Testing
@Test
func previousStoreCanBeOpened() throws {
let storeURL = try copyBaselineStoreToTemporaryDirectory()
let container = makeProductionPersistentContainer(storeURL: storeURL)
try container.loadPersistentStores()
}

If a migration is missing or incompatible, loading the store throws an error and the test fails.

To cover both the full migration chain and the most realistic upgrade path, a strategy can be to maintain two baseline stores:

  1. A store created several versions ago (tests the entire chain)
  2. A store from the last released version (tests a common upgrade path)

Maintaining the "last release" store introduces additional effort, since it needs to be updated after each shipped schema change. Whether this is worth it depends on the project. For teams that already perform thorough manual upgrade testing, keeping a single older baseline may provide sufficient safety.

How do I create a database file?

The easiest way to create such a file is to run the app and let it create its database naturally. After that, the database file can be copied from the simulator or device container and added to the test target.

The exact file depends on the persistence framework:

  • with Core Data, it is typically a .sqlite file (and possibly its -wal and -shm companions).
  • with SwiftData, it is also backed by SQLite and follows the same structure.
  • with Realm, it is a .realm file.

On the iOS simulator, the database can usually be found inside the app container under:

~/Library/Developer/CoreSimulator/Devices/<DeviceID>/data/Containers/Data/Application/<AppID>/

Can the baseline store be generated automatically?

Updating and replacing the baseline store after a schema change does involve some manual work. At first glance, it might seem cleaner to generate the baseline store automatically during the test run instead of maintaining fixture files. Conceptually, that could look something like this:

import Testing
@Test
func migrationFromPreviousSchemaSucceeds() throws {
let oldStoreURL = try createStoreWithPreviousSchema()
try openStoreWithCurrentSchema(at: oldStoreURL)
}

It's easy to automate creating a store file. The hard part is creating it with an older schema.

To programmatically generate a "v2 store", the test must still have access to the "v2 model definition". For example, in Core Data, that usually means keeping older .xcdatamodel versions around and loading them explicitly. In Realm, we would need to keep the older objects somehow.

So it introduces maintenance work of a different kind:

  • Older model versions must remain in the project and stay consistent with what was actually shipped.
  • Tests need a reliable way to load a specific historical model version.

Because of that, in most cases, storing baseline files is the clearer and easier solution.

Sponsorship logo
Preparing for a technical iOS job interview
Preparing for a technical iOS Job Interview with over 300 questions & answers. Covering Swift & Objective-C, SwiftUI & UIKit, Combine, HTTP Networking, iOS File System, Core Data, Concurrency with async/await, Security, Automated Testing, Dependency Management, AI & Machine Learning and more.
LEARN MORE
Sponsorship logo
Architecture & Design Patterns for iOS
This book is a practical guide to essential architectural principles and design patterns for iOS development. It covers strategies for building maintainable apps with Swift and SwiftUI, including dependency injection, navigation, common patterns, and modularization.
LEARN MORE
Sponsorship logo
Become a sponsor of tanaschita.com
By publishing an article on different iOS topics every week, tanaschita.com is constantly growing in the developer community and may provide a great audience for you as a sponsor.
CLICK TO LEARN MORE

Newsletter

Image of a reading marmot
Subscribe

Like to support my work?

Say hi

Related tags

Articles with related topics

accessibility

swiftui

testing

xcode

ios

Automating accessibility audits for SwiftUI apps with XCTest

Learn how to detect common accessibility issues automatically as part of a UI test suite.

28 Sep 2026 · 5 min read

Latest articles and tips

© 2026 tanaschita.com

Privacy policy

Impressum