Skip to main content

Migration Testing

Migration testing validates that your schema migrations work correctly by loading real frozen databases and attempting to migrate them. This catches crashes and data corruption before production.

The Problem

Fresh Installs vs. Migrations

When you test your app in the simulator, you’re almost always testing fresh installs:
The result: Your tests pass, but real users crash.

Migration Crashes Are Silent Killers

Production crashes from broken migrations:
This error means you modified a shipped schema. Fresh installs don’t catch this because they skip migration entirely.

Data Corruption Is Worse Than Crashes

Sometimes migrations “succeed” but silently corrupt data:
  • Non-optional properties added without defaults → undefined behavior
  • Transformable properties with changed types → old data becomes nil
  • Properties removed → user data silently dropped
  • Type conversions fail → data loss
Users don’t know their data is corrupted until it’s too late.

The Solution: Load Real Databases

FreezeRay tests migrations using actual frozen databases from shipped versions:
If your migration is broken, you get the crash in your test suite where you can debug it.

How It Works

1. Frozen Fixtures as Test Data

When you freeze a schema, FreezeRay captures the SQLite database:
These fixtures are committed to your repo and included in your test bundle.

2. Generate Migration Tests

When you start working on v2.0.0, generate the migration test:
This creates a migration test template:

3. Runtime Migration Testing

FreezeRayRuntime.testMigration() does the following:
  1. Copies the frozen v1.0.0 database to a temporary location
  2. Creates a ModelContainer with your SchemaMigrationPlan
  3. Attempts migration from v1.0.0 → v2.0.0
  4. Throws if migration fails or crashes
If migration succeeds, control returns to your test where you can add custom validation.

Test Types

Generated Migration Tests

Created when you start working on a new schema version:
What it tests:
  • Migration doesn’t crash
  • SwiftData can open the migrated database
  • Basic migration mechanics work
What it doesn’t test (you add this):
  • Data integrity validation
  • Custom migration logic correctness
  • Performance

Custom Data Validation

You should add validation to check data integrity:

Multi-Hop Migrations

SwiftData runs migrations sequentially: V1 → V2 → V3 FreezeRay tests each hop:
Why test multi-hop?
  • Users might skip app versions (1.0 user updates directly to 3.0)
  • Cumulative migrations can expose bugs not visible in single hops
  • Data transformations across multiple hops can cause unexpected issues

Common Migration Issues

Issue 1: Modified Shipped Schema

Error:
Fix: Don’t modify shipped schemas. Create AppSchemaV2 instead.

Issue 2: Missing Models

Error:
Fix: Include ALL models in each schema version.

Issue 3: Data Loss in Custom Migrations

No error - migration succeeds, but data is gone! Fix: Add test validation to catch data loss:

Best Practices

Always customize scaffolded tests. The TODO markers are reminders to add data validation.

Add Meaningful Assertions

Test Edge Cases

Run Tests in CI

Migration tests should run on every PR:

Troubleshooting

”Fixture not found” error

Cause: Fixtures not included in test bundle Fix:
  1. Add FreezeRay/ folder to test target resources
  2. Ensure folder reference is “folder” (blue) not “group” (yellow)

Migration test passes locally, fails in CI

Cause: Fixtures not committed to git Fix:

Test crashes with “cannot open database”

Cause: Database file permissions in test bundle Fix: Copy fixture to writable location before testing (FreezeRayRuntime does this automatically)

Next Steps

Testing Migrations Guide

Step-by-step tutorial for custom validation

Drift Detection

Catch schema changes before testing migrations

Schema Freezing

Learn how to create frozen fixtures

CI Integration

Run migration tests in your CI pipeline