Recently I started playing with SwiftData and Testing framework.
I stumbled across an interesting crash. It occurs when inserting the model into ModelContext
. However it happens only if I create my ModelContainer
in the initializer. When it's created directly inside the test method, everything works fine.
Maybe someone could help me understand why it is so.
Thanks!
Here's the minimum reproducible example:
import Testing
import SwiftData
@MainActor @Suite
class StoreTests {
var context: ModelContext!
var store: Store!
init() {
let modelContainer = try! ModelContainer(
for: Model.self,
configurations: .init(isStoredInMemoryOnly: true)
)
context = modelContainer.mainContext
store = Store(context: context)
}
@Test
func testThatStoresModel_Init() throws {
let model = Model(id: 0)
try store.save(model) // Crashes with Thread 1: EXC_BREAKPOINT
// (...)
}
@Test
func testThatStoresModel_Inline() throws {
let modelContainer = try! ModelContainer(
for: Model.self,
configurations: .init(isStoredInMemoryOnly: true)
)
let context = modelContainer.mainContext
let store = Store(context: context)
let model = Model(id: 0)
try store.save(model) // Passes test
// (...)
}
}
@Model
class Model {
var id: Int
init(id: Int) {
self.id = id
}
}
class Store {
let context: ModelContext
init(context: ModelContext) {
self.context = context
}
func save(_ model: Model) throws {
context.insert(model)
try context.save()
}
}
The root cause is basically the same as this question. You need to keep a strong reference to the model container, or else it will get deinitialised.
The ModelContainer
you created in init
gets deinitialised after init
returns, and you end up with a "hanging" ModelContext
that has no container to insert into, when the test function is invoked.
If you create the ModelContainer
in the test function, it will live until the test function returns.
If you keep a reference to the model container in the suite class,
let container: ModelContainer // keep the model container alive!
let context: ModelContext
let store: Store
init() {
container = try! ModelContainer(
for: Model.self,
configurations: .init(isStoredInMemoryOnly: true)
)
context = container.mainContext
store = Store(context: context)
}
Then testThatStoresModel_Init
will pass as expected.