swiftxcodexctestcaserxtest

Is `tearDown` calling necessary?


Something consider me a long time. Lets say that we have written test class:

final class BearerTokenManagerTests: XCTestCase {

    private var bearerTokenManager: BearerTokenManager!

    private var bearerTokenProvider: BearerTokenProvider!
    private var stubKeyValueStore: KeyValueStoreDummyStub!

    private var scheduler: TestScheduler!
    private var disposeBag: DisposeBag!

    override func setUp() {
        super.setUp()

        stubKeyValueStore = KeyValueStoreDummyStub()
        bearerTokenProvider = BearerTokenProvider(keyValueStore: stubKeyValueStore)

        bearerTokenManager = BearerTokenManager(bearerTokenProvider: bearerTokenProvider)

        scheduler = TestScheduler(initialClock: 0)
        disposeBag = DisposeBag()
    }

    override func tearDown() {
        stubKeyValueStore = nil
        bearerTokenProvider = nil
        bearerTokenManager = nil

        scheduler = nil
        disposeBag = nil

        super.tearDown()
    }

    func test_bearerToken_observeChanges() {
        let bearerToken = scheduler.createObserver(BearerTokenManagerType.BearerToken.self)

        bearerTokenManager.bearerToken
            .bind(to: bearerToken)
            .disposed(by: disposeBag)

        scheduler.start()

        // every update should be saved in key value store

        bearerTokenManager.update(bearerToken: "123")
        XCTAssertEqual(stubKeyValueStore.string(forKey: "BearerToken"), "123")

        bearerTokenManager.update(bearerToken: "456")
        XCTAssertEqual(stubKeyValueStore.string(forKey: "BearerToken"), "456")

        bearerTokenManager.update(bearerToken: "789")
         XCTAssertEqual(stubKeyValueStore.string(forKey: "BearerToken"), "789")

        // every udpate should be emited

        XCTAssertEqual(bearerToken.events, [
            .next(0, nil), // by default (on start) token equal to nil

            .next(0, "123"),
            .next(0, "456"),
            .next(0, "789"),
        ])
    }
}

Is tearDown calling for cleaning purposes necessary?

Why I thinking it could be not necessary:

Why I not sure

Could someone share their experience? Do you clean up stuff in tearDown? Is reseting properties in setUp enough?


Solution

  • Quick answer

    According to this article: https://qualitycoding.org/xctestcase-teardown/

    XCTest creates a new XCTestCase instance for every individual test invocation but doesn't deinit any of them after completion.

    Demo

    I have created demo app in Xcode 11.7 and behavior is still the same.

    System Under Test

    import UIKit
    
    var counter = 0
    
    class ViewController: UIViewController {
        
        init() {
            super.init(nibName: nil, bundle: nil)
            counter += 1;
            print("Created ViewController, currently living: \(counter)")
        }
        
        required init?(coder: NSCoder) {
            super.init(coder: coder)
        }
        
        deinit {
            counter -= 1;
            print("Destroyed ViewController, currently living: \(counter)")
        }
    
        override func viewDidLoad() {
            super.viewDidLoad()
            // Do any additional setup after loading the view.
        }
    }
    

    TestCase with tearDown()

    class ViewControllerTests: XCTestCase {
    
        var vc: ViewController!
        
        override func setUp() {
            super.setUp()
            vc = ViewController()
        }
        
        override func tearDown() {
            vc = nil
            super.tearDown()
        }
        
        func test_a() {
            XCTAssert(true == true)
        }
        
        func test_b() {
            XCTAssert(true == true)
        }
        
        func test_c() {
            XCTAssert(true == true)
        }
    }
    

    Output:

    Test Suite 'ViewControllerTests' started at 2020-09-13 14:44:15.889
    Test Case '-[DemoTests.ViewControllerTests test_a]' started.
    Created ViewController, currently living: 1
    Destroyed ViewController, currently living: 0
    Test Case '-[DemoTests.ViewControllerTests test_a]' passed (0.001 seconds).
    Test Case '-[DemoTests.ViewControllerTests test_b]' started.
    Created ViewController, currently living: 1
    Destroyed ViewController, currently living: 0
    Test Case '-[DemoTests.ViewControllerTests test_b]' passed (0.000 seconds).
    Test Case '-[DemoTests.ViewControllerTests test_c]' started.
    Created ViewController, currently living: 1
    Destroyed ViewController, currently living: 0
    Test Case '-[DemoTests.ViewControllerTests test_c]' passed (0.000 seconds).
    Test Suite 'ViewControllerTests' passed at 2020-09-13 14:44:15.891.
         Executed 3 tests, with 0 failures (0 unexpected) in 0.002 (0.003) seconds
    Test Suite 'sdadasTests.xctest' passed at 2020-09-13 14:44:15.892.
         Executed 3 tests, with 0 failures (0 unexpected) in 0.002 (0.003) seconds
    Test Suite 'Selected tests' passed at 2020-09-13 14:44:15.892.
         Executed 3 tests, with 0 failures (0 unexpected) in 0.002 (0.004) seconds
    

    TestCase without tearDown()

    class ViewController2Tests: XCTestCase {
    
        var vc: ViewController!
        
        override func setUp() {
            vc = ViewController()
        }
        
        func test_a() {
            XCTAssert(true == true)
        }
        
        func test_b() {
            XCTAssert(true == true)
        }
        
        func test_c() {
            XCTAssert(true == true)
        }
    }
    

    Output:

    Test Suite 'ViewController2Tests' started at 2020-09-13 14:47:43.067
    Test Case '-[sdadasTests.ViewController2Tests test_a]' started.
    Created ViewController, currently living: 1
    Test Case '-[DemoTests.ViewController2Tests test_a]' passed (0.001 seconds).
    Test Case '-[DemoTests.ViewController2Tests test_b]' started.
    Created ViewController, currently living: 2
    Test Case '-[DemoTests.ViewController2Tests test_b]' passed (0.000 seconds).
    Test Case '-[DemoTests.ViewController2Tests test_c]' started.
    Created ViewController, currently living: 3
    Test Case '-[DemoTests.ViewController2Tests test_c]' passed (0.000 seconds).
    Test Suite 'ViewController2Tests' passed at 2020-09-13 14:47:43.070.
         Executed 3 tests, with 0 failures (0 unexpected) in 0.002 (0.003) seconds
    Test Suite 'sdadasTests.xctest' passed at 2020-09-13 14:47:43.070.
         Executed 3 tests, with 0 failures (0 unexpected) in 0.002 (0.003) seconds
    Test Suite 'Selected tests' passed at 2020-09-13 14:47:43.071.
         Executed 3 tests, with 0 failures (0 unexpected) in 0.002 (0.004) seconds
    

    Long answer

    Like you can see in the example without tearDown() initialize inside setUp() does not assign new object to the same property. Every tests create individual instance and doesn't deinit it after completion. Only the end of the whole XCTestCase instances will be deallocated.

    In small project it probably doesn't matter that much.

    But if you have a lot of tests in one XCTestCase and you are creating a lot of data in setUp() (e.g. stubs that takes a lot of memory) you should consider using tearDown() because every test will keep it own copy of data from setUp() until whole XCTestCase will be completed and you can end up with memory limits issues.