swiftswiftuixctestswiftdataswift-data-modelcontext

SwiftData Unit Testing EXC_BREAKPOINT on insert


Hello,

I'm trying to unit test a SwiftData Model, but I'm facing a weird situation and couldn't find an answer to the problem.

The first solution I'll show you does not work, the second one does.

First of all, here's my @Model :

@Model
class Movie {
    var name: String

    init(name: String) {
        self.name = name
    }
}

First solution (doesn't work) :

var modelContainer: ModelContainer {
    do {
        let container = try ModelContainer(for: Movie.self, configurations: ModelConfiguration(isStoredInMemoryOnly: true))
        return container
    } catch {
        fatalError("Failed to create container.")
    }
}

@MainActor
final class Tests: XCTestCase {
    private var context: ModelContext!

    override func setUp() {
        context = modelContainer.mainContext
    }

    func testCountShouldBeOne() {
        let movie = Movie(name: "NewMovie")
        context.insert(movie) // Thread 1: EXC_BREAKPOINT

        do {
            let descriptor = FetchDescriptor<Movie>()
            let movies = try context.fetch(descriptor)

            XCTAssertEqual(movies.count, 1)
        } catch {
            XCTFail()
        }
    }
}

Second solution (works) :

(modelContainer remains the same)

@MainActor
final class Tests: XCTestCase {
    private var container: ModelContainer!

    override func setUp() {
        container = modelContainer
    }

    func testCountShouldBeOne() {
        let movie = Movie(name: "NewMovie")
        container.mainContext.insert(movie)

        do {
            let descriptor = FetchDescriptor<Movie>()
            let movies = try container.mainContext.fetch(descriptor)

            XCTAssertEqual(movies.count, 1)
        } catch {
            XCTFail()
        }
    }
}

So the difference is I now have to call container.mainContext for SwiftData operations, and it works but in the first solution, where I had : context.something, I got the EXC_BREAKPOINT.

Does somebody know why?

Thank you !


Solution

  • In both cases, modelContainer is a computed property that always returns a new ModelContainer when you access it.

    In the first case, you just get the modelContext out of the ModelContainer, without holding onto the ModelContainer. The ModelContainer gets deallocated as a result and you are now left with a ModelContext that doesn't have an associated container.

    In the second case, you do store the ModelContainer as a stored property of the Tests class, so the test case is keeping it alive.

    The exact syntax you use to access the model context isn't really relevant. The key point here is whether or not you are keeping the ModelContainer returned by the computed property alive. If you feel container.mainContext is too long, you can write a computed property like this in the Tests class.

    var modelContext: ModelContext { container.mainContext }