1

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:

  • Before every next test case setUp resets everything.
  • When tests in BearerTokenManagerTests ends then everything should deallocates

Why I not sure

  • Assuming that „When tests in BearerTokenManagerTests ends then everything should deallocates” could be wrong
  • I worried about RxScheduler side effects
  • Something I don't know yet

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

Kamil Harasimowicz
  • 4,684
  • 5
  • 32
  • 58
  • primary opinion based, but in such case you may not need the `tearDown()` at all; since you are not doing any special 'reset' for your next run. – holex Dec 13 '19 at 16:59

1 Answers1

4

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.

Kamil Harasimowicz
  • 4,684
  • 5
  • 32
  • 58
  • Very interesting, but your tests didn't actually use `setUp` for anything and it's a little hard to see what that has to do with what you are saying here. We didn't actually learn _anything_ about `setUp` and `tearDown` from this. Also keep in mind there's an instance version and a class version of those methods. – matt Sep 11 '20 at 17:36
  • I will provide better example – Kamil Harasimowicz Sep 11 '20 at 17:42
  • Well, I don't know if you really have to. What you've shown is that `setUp` and `tearDown` are for externalities. They are unaffected by all this, and clearly you _do_ want to reset your externalities after each test so that things are not affected by previous tests. We're talking apples and oranges here. What you're saying is very interesting but it has nothing at all to do with proper use of `setUp` and `tearDown`. – matt Sep 11 '20 at 17:49
  • And your conclusion is wrong. You say "Every object you create in setUp should be destroyed in tearDown". But if you mean, as in your original question, every instance property you configure in `setUp`, you do _not_ need to undo that in `tearDown`, because you will get a new instance and a new instance property when the next test runs. So after all that, you came to exactly the wrong answer to your original question. – matt Sep 11 '20 at 17:52
  • @matt I edited my answer. I provided better example and I changed my conclusion. – Kamil Harasimowicz Sep 13 '20 at 13:06