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.
When you test your app in the simulator, you’re almost always testing fresh installs:
Copy
// Simulator test (fresh install):// 1. No existing database// 2. SwiftData creates NEW database with current schema// 3. No migration happens// ✅ Test passes// Production user (existing database):// 1. Existing database with v1.0.0 schema// 2. SwiftData must MIGRATE v1.0.0 → v2.0.0// 3. Migration fails or corrupts data// ❌ App crashes on launch
The result: Your tests pass, but real users crash.
FreezeRay tests migrations using actual frozen databases from shipped versions:
Copy
@Test("Migrate v1.0.0 → v2.0.0")func testMigrateV1toV2() throws { // Loads the REAL v1.0.0 database from FreezeRay/Fixtures/1.0.0/ // Attempts migration using your SchemaMigrationPlan // Crashes in tests if migration is broken try FreezeRayRuntime.testMigration( from: AppSchemaV1.self, to: AppSchemaV2.self, migrationPlan: AppMigrations.self )}
If your migration is broken, you get the crash in your test suite where you can debug it.
// AUTO-GENERATED by FreezeRay CLI// This file is scaffolded once and owned by you. Customize as needed.@Suite(.serialized)struct MigrateV1_0_0toV2_0_0_Tests { @Test("Migrate v1.0.0 → v2.0.0") func testMigrateV1_0_0toV2_0_0() throws { // Test the migration using FreezeRayRuntime try FreezeRayRuntime.testMigration( from: AppSchemaV1.self, to: AppSchemaV2.self, migrationPlan: AppMigrations.self ) // TODO: Add data integrity checks here // Example: // - Verify data is preserved during migration // - Check that new fields have default values // - Validate relationship updates // - Ensure no data loss for critical fields }}
You should add validation to check data integrity:
Copy
@Test("Migrate v1.0.0 → v2.0.0")func testMigrateV1_0_0toV2_0_0() throws { try FreezeRayRuntime.testMigration( from: AppSchemaV1.self, to: AppSchemaV2.self, migrationPlan: AppMigrations.self ) // ✅ Add custom validation let container = try ModelContainer( for: AppSchemaV2.self, configurations: ModelConfiguration(/* migrated db */) ) let context = container.mainContext let users = try context.fetch(FetchDescriptor<DataV2.User>()) // Verify record counts #expect(users.count == 10, "Expected 10 users after migration") // Check required fields preserved for user in users { #expect(!user.name.isEmpty, "User name should not be empty") #expect(!user.email.isEmpty, "User email should not be empty") } // Validate new fields have defaults for user in users { #expect(user.createdAt != nil, "New createdAt field should have default") }}
// ❌ You modified AppSchemaV1 after shippingenum AppSchemaV1: VersionedSchema { static var models: [any PersistentModel.Type] { [User.self, Post.self] // Added Post after shipping! }}
Error:
Copy
Cannot use staged migration with an unknown model version
@Test func testMigrationWithEmptyDatabase() throws { // What if database has no records?}@Test func testMigrationWithMaxRecords() throws { // What if database has thousands of records?}@Test func testMigrationWithCorruptedData() throws { // What if transformable data is malformed?}