Core Data In Memory Store

Speed up your tests and SwiftUI previews by creating your Core Data stack with an in-memory store.

The Core Data stack uses an on-disk SQLite store by default. That causes some extra work when running tests as you need to reset the store to a known state before each test. It’s also slow which is a pain if you’re working with SwiftUI previews.

In Memory Store

The old way of creating an in-memory store was to change the store type in the persistent store descriptor before loading the store. The default is NSSQLiteStoreType but we can switch to NSInMemoryStoreType:

storeDescription.type = NSInMemoryStoreType

There’s nothing I can find in the documentation but Apple showed a different way during WWDC 2018. I was only reminded of it when looking at the project templates after creating a new Xcode Core Data project. The trick is to set the URL of the persistent store description to /dev/null before loading the store:

storeDescription.url = URL(fileURLWithPath: "/dev/null")

This still uses an SQLite store but we keep it in memory instead of writing it to disk. As well as being faster this also gives us a clean store each time.

NSPersistentContainer

I’m using a subclass of NSPersistentContainer to load and configure my Core Data store. That’s a convenient place to create a custom initializer that has an option to use an in memory store:

public final class CoreDataContainer: NSPersistentContainer {
  public init(name: String, bundle: Bundle = .main, inMemory: Bool = false) {
    guard let mom = NSManagedObjectModel.mergedModel(from: [bundle]) else {
      fatalError("Failed to create mom")
    }
    super.init(name: name, managedObjectModel: mom)
    configureDefaults(inMemory)
  }
}

Then when I configure the store I change the url of the store description:

private func configureDefaults(_ inMemory: Bool = false) {
  if let storeDescription = persistentStoreDescriptions.first {
    storeDescription.shouldAddStoreAsynchronously = true        
    if inMemory {
      storeDescription.url = URL(fileURLWithPath: "/dev/null")
      storeDescription.shouldAddStoreAsynchronously = false
    }
  }
}

Note that I default to loading my store asynchronously to allow for slow store migrations. I don’t need to worry about that with an in-memory store. Since the store is always empty I can switch back to adding the store synchronously which keeps thing simple.

Unit Testing

In my unit tests I can now create the core data container with an in-memory store:

final class CoreDataContainerTests: XCTestCase {
  private var container: CoreDataContainer!

  override func setUpWithError() throws {
    let container = CoreDataContainer(name: "Model", inMemory: true)
    container.loadPersistentStores { description, error in
      XCTAssertNil(error)
    }
  }
    
  override func tearDownWithError() throws {
    container = nil
  }
    
  func testPerformRequest() throws {
    let context = container.viewContext
    ...
  }
}

Note: I still keep some tests that use an on-disk store to reproduce the app environment.

SwiftUI Previews

This approach also works great for SwiftUI previews. I’ve been experimenting with keeping my Core Data objects contained in a store that publishes updates with Combine:

public final class WorldStore: ObservableObject {
  @Published public private(set) var countries = [Country]()
  @Published public private(set) var error: Error?

I’m creating the core data container when I initialize the store so I can again include an option for an in-memory store:

  private let dataContainer: CoreDataContainer

  public init(inMemory: Bool = false) {
    dataContainer = CoreDataContainer(name: "World", inMemory: inMemory)
    dataContainer.loadPersistentStores { ... }
  }
}

I add my SwiftUI preview data as development assets:

extension Country {
  static var previewData = [...]
}

I follow a similar approach to create a preview version of my store that is pre-populated with sample data:

extension WorldStore {
  static var preview: WorldStore = {
    let store = WorldStore(inMemory: true)
    store.update(Country.previewData)
    return store
  }()
}

I can then use this in my SwiftUI previews:

struct WorldView_Previews: PreviewProvider {
  static var previews: some View {
    WorldView()
      .environmentObject(WorldStore.preview)
  }
}

Further Details