How can I unit test a Core Data migration?
Asked Answered
S

2

18

I'm building a mapping model for my migration with a custom entity migration policy, and I'd really like to build some unit tests for this migration. The migration seems to work correctly when I run the app, but my NSEntityMigrationPolicy subclass methods are not called at all when I run the migration via a unit test.

I'm using Xcode's built-in OCUnit framework.

My test code:

- (void)test1to2Migration_appIdentifierMoved {
  [self createVersion1Store];

  // TODO Perform migration
  NSManagedObjectModel *version1Model = [self version1Model];
  NSManagedObjectModel *version2Model = [self version2Model];

  NSError *error = nil;
  NSMappingModel *mappingModel = [NSMappingModel
      inferredMappingModelForSourceModel:version1Model
      destinationModel:version2Model error:&error];
  STAssertNotNil(mappingModel, @"Error finding mapping model: %@", error);

  NSMigrationManager *migrationManager =
      [[[NSMigrationManager alloc]
        initWithSourceModel:version1Model
        destinationModel:version2Model]
       autorelease];

  BOOL migrationSucceeded =
      [migrationManager migrateStoreFromURL:self.version1StoreURL
           type:NSSQLiteStoreType
           options:nil
           withMappingModel:mappingModel
           toDestinationURL:self.version2StoreURL
           destinationType:NSSQLiteStoreType
           destinationOptions:nil
           error:&error];
  STAssertTrue(migrationSucceeded, @"Error migrating store: %@", error);

  // TODO Verify appIdentifier is moved from Project to its Tests

  [self deleteTempStores];
}

My mapping model specifies a custom NSEntityMigrationPolicy that defines the -createRelationshipsForDestinationInstance:entityMapping:manager:error: method, but my policy is never called from the unit test. When I run the migration, the model is modified to the new version -- the expected attributes show up in the right places.

Any ideas how I can get my migration policy to work in a unit test?

Spoor answered 14/8, 2012 at 19:30 Comment(0)
S
4

The problem turned out to be in the line

NSMappingModel *mappingModel = [NSMappingModel
  inferredMappingModelForSourceModel:version1Model
  destinationModel:version2Model error:&error];

If I change it to

NSMappingModel *mappingModel = [NSMappingModel
  mappingModelFromBundles:@[[NSBundle bundleForClass:[MyTestClass class]]]
  forSourceModel:version1Model destinationModel:version2Model];

then the test functions correctly.

Spoor answered 17/8, 2012 at 18:57 Comment(0)
R
5

Swift 3

Replace variables modelName and modelNameVersionFormatString for your models file names

import XCTest
import CoreData

class RCCoreDataMigrationTests: XCTestCase {

    private let storeType = NSSQLiteStoreType
    private let modelName = "Model"
    private let modelNameVersionFormatString = "Model-%@"

    private func storeURL(_ version: String) -> URL? {
        let storeURL = URL(fileURLWithPath: "\(NSTemporaryDirectory())\(version).sqlite" )
        return storeURL
    }

    private func createObjectModel(_ version: String) -> NSManagedObjectModel? {
        let bundle = Bundle.main
        let managedObjectModelURL = bundle.url(forResource: modelName, withExtension: "momd")
        let managedObjectModelURLBundle = Bundle(url: managedObjectModelURL!)
        let modelVersionName = String(format: modelNameVersionFormatString, version)
        let managedObjectModelVersionURL = managedObjectModelURLBundle!.url(forResource: modelVersionName, withExtension: "mom")
        return NSManagedObjectModel(contentsOf: managedObjectModelVersionURL!)
    }

    private func createStore(_ version: String) -> NSPersistentStoreCoordinator {
        let model = createObjectModel(version)
        let storeCoordinator = NSPersistentStoreCoordinator(managedObjectModel: model!)
        try! storeCoordinator.addPersistentStore(ofType: storeType,
                                                 configurationName: nil,
                                                 at: storeURL(version),
                                                 options: nil)
        return storeCoordinator
    }

    private func migrateStore(fromVersionMOM: String, toVersionMOM: String) {
        let store = createStore(fromVersionMOM)
        let nextVersionObjectModel = createObjectModel(toVersionMOM)!
        let mappingModel = NSMappingModel(from: [Bundle.main], forSourceModel: store.managedObjectModel, destinationModel: nextVersionObjectModel)!
        let migrationManager = NSMigrationManager(sourceModel: store.managedObjectModel, destinationModel: nextVersionObjectModel)
        do {
            try migrationManager.migrateStore(from: store.persistentStores.first!.url!,
                                              sourceType: storeType,
                                              options: nil,
                                              with: mappingModel,
                                              toDestinationURL: storeURL(toVersionMOM)!,
                                              destinationType: NSSQLiteStoreType,
                                              destinationOptions: nil)
        } catch {
            print("Error: \(error)")
            XCTAssertNil(error)
        }
        try! FileManager.default.removeItem(at: storeURL(toVersionMOM)!)
        try! FileManager.default.removeItem(at: storeURL(fromVersionMOM)!)
    }

    func testMigratingStores() {
        migrateStore(fromVersionMOM: "1486", toVersionMOM: "1487")
    }
}
Recently answered 4/3, 2017 at 3:43 Comment(2)
my mapping model always returns nil. Do you know why ? :/Pannier
@Pannier I guess the problem is that your test bundle is not the same bundle containing your data model. I have the same problem, but do not yet know how to solve this.Smelser
S
4

The problem turned out to be in the line

NSMappingModel *mappingModel = [NSMappingModel
  inferredMappingModelForSourceModel:version1Model
  destinationModel:version2Model error:&error];

If I change it to

NSMappingModel *mappingModel = [NSMappingModel
  mappingModelFromBundles:@[[NSBundle bundleForClass:[MyTestClass class]]]
  forSourceModel:version1Model destinationModel:version2Model];

then the test functions correctly.

Spoor answered 17/8, 2012 at 18:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.