Search code examples
swiftmemory-leaksquick-nimble

Swift Quick framework memory leak


I'm using Quick to test my Swift code. However, I think it doesn't release objects defined in describe scope:

class MyClass {
    deinit {
        print(self, #function)
    }
}

final class MyClassSpec: QuickSpec {
    override func spec() {
        describe("") {
            let foo = MyClass()
            it("") {
                print(foo)
                expect(true).to(beTrue())
            }
        }
    }
}

I don't see any output from print inside deinit, and a debug breakpoint inside the deinit does not get catched. If I move foo inside it, the deinit is called.

Is this a bug in Quick, or is it normal for deinit not to be called in a test suite?


Solution

  • Apparently the code I wrote was not only retaining the object but was also an anti-pattern.

    Even a plain old XCTestCase retains an object:

    class MyClass {
        deinit {
            print(self, #function)
        }
    }
    
    final class MyClassTest: XCTestCase {
        let foo = MyClass()
    
        func testMyClass() {
            print(foo)
            XCTAssert(true)
        }
    }
    

    deinit is not called for foo.

    This is due to a nature of XCTestCaseit never really gets deinited. So one should always use setUp & tearDown to manage everything (or more accurately, objects with reference semantics).

    I believe this directly translates to QuickSpec as well, so I should always use beforeEach & afterEach in order to manage the objects. To "fix" the problem, I should test like:

    final class MyClassSpec: QuickSpec {
        override func spec() {
            describe("") {
                let foo: MyClass!
    
                beforeEach { foo = MyClass() }
                afterEach { foo = nil }
    
                it("") {
                    print(foo)
                    expect(true).to(beTrue())
                }
            }
        }
    }