4

I want to test such a class:

class Foo {
    var number: Int = 0
}

In the iOS unit test, normally test case should be:

class FooTests: XCTestCase {

    var foo: Foo!

    override func setUp() {
        foo = Foo()
    }

    override func tearDown() {
        foo = nil
    }

    func testAbc() {
        print(foo.number)
        foo.number = 10
    }

    func testBCD() {
        print(foo.number)
    }

}

Then how about

class FooTests: XCTestCase {

     let foo = Foo()

     func testAbc() {
        print(foo.number)
        foo.number = 10
    }

    func testBCD() {
        print(foo.number)
    }

}

I see the output are all 0 which means when each start test case the foo seems is initialised again. Just like use setUp and tearDown.

Are both way the same?

EDIT: Thanks to @Anton 's answer, I even tested without setUp but with tearDown, then same as use both setUp and tearDown.

    var foo: Foo! = Foo()

    override func tearDown() {
        foo = nil
    }
William Hu
  • 15,423
  • 11
  • 100
  • 121
  • 1
    There're `class`(`static`) and `instance` `setUp` and `tearDown` methods. `static` ones are called once, `instance` ones are called before and after **each** test. Reference: https://developer.apple.com/documentation/xctest/xctestcase/understanding_setup_and_teardown_for_test_methods – user28434'mstep May 06 '19 at 12:32
  • 1
    https://qualitycoding.org/xctestcase-teardown/ – Ahmad F May 06 '19 at 13:01

2 Answers2

7

Note that

var foo: Foo! = Foo()

override func tearDown() {
    foo = nil
}

is still incorrect. The problem is that the Foo will be created when the test is instantiated. All tests are instantiated at once. So if there are 5 test cases in this suite, that means there will be 5 instances of FooTests, each with their own Foo. And all before any tests are run.

This can cause problems, particularly if Foo registers itself with a shared controller such as NotificationCenter.

Instead, do

private var foo: Foo!

override func setUp() {
    super.setUp()
    foo = Foo()
}

override func tearDown() {
    foo = nil
    super.tearDown()
}

This guarantees that foo will be created in the context of a running test case.

Bhargav Rao
  • 50,140
  • 28
  • 121
  • 140
Jon Reid
  • 20,545
  • 2
  • 64
  • 95
  • " And all before any tests are run." Do you mean the 5 instances are there 1 Just after typing the 5 test cases 2. Just after click the test button for `FooTests`(run all test cases) 3. Click any of the 5 test cases to run the test. (Even click one test case, there are 5 instances there already?) Thanks! – William Hu May 08 '19 at 07:17
  • Yes. Even if you "run a single test," I'm pretty sure it will instantiate every single test first, then run the one you specified. – Jon Reid May 09 '19 at 00:33
3

They behaviors like same but have a difference. To understand the difference you can add to your test class init and deinit methods:

class Foo {
    var number: Int = 0

    init() {
        print("Init")
    }

    deinit {
        print("Deinit")
    }
}

Now you will see that at first case init and deinit invokes at each test. Because setUp and tearDown is instance method and invokes before and after run the test.

But at second case init is invoked twice during create test suit. It happens because you use a default initializer for foo constant. It invokes during the initialization of Test Case which happens before run any test.

So, better to use the first option. Because if the instance under a test use some global state or have side effects then you may get flaky tests. They lives in memory in parallel.

Diagram

Anton Vlasov
  • 1,372
  • 1
  • 10
  • 18