Unit Test Setup And Teardown

There’s a growing number of setup and teardown methods available to your unit tests. Which should you use?

Setup and Teardown

When you add a unit test bundle to your iOS app you get a default test case class that looks something like this (using Xcode 14):

import XCTest
final class XCTTests: XCTestCase {
  override func setUpWithError() throws {
    // Put setup code here. This method is called before
    // the invocation of each test method in the class.
  }

  override func tearDownWithError() throws {
    // Put teardown code here. This method is called after
    // the invocation of each test method in the class.
  }

  func testExample() throws { ... }
}

The XCTestCase class inherits setup and teardown methods from XCTest. The XCTest runner calls the setup method before running a test method, and the teardown method after. Using Xcode 14, the default template uses throwing versions but you can choose from three versions to override in your test class:

// In order of execution
func setUp() async throws
func setUpWithError() throws
func setUp()

// test method runs here

func tearDown()
func tearDownWithError() throws
func tearDown() async throws

Which one should you use?

  • The plain setUp() and tearDown methods date back to Xcode 7 and allows for synchronous test preparation with no error handling.

  • Apple added the throwing setUpWithError() and tearDownWithError methods in Xcode 11.4. They are still synchronous but do allow the setup/teardown code to throw an error marking the running test as failed. See XCTest Error Handling Improvements for details.

  • Apple added asynchronous versions of the throwing setup and teardown methods in Xcode 13. If you need these methods to run on the Main actor you must specify @MainActor or they will run on an arbitrary actor.

  • The test runner calls the async setUp first, then setUpWithError and finally the non-throwing setUp. On teardown the order reverses.

  • Apple has not deprecated any of the older methods. Use whichever is suitable to your test case.

Class Methods

There’s one more pair of setup/teardown methods I should mention. The XCTestCase class also has two class methods that you can override:

class func setUp()
class func tearDown()

These are the first and last methods called by XCTest. The class setUp happens before any of the individual test cases and the class tearDown after all test cases have finished.

TearDown Blocks

Finally there’s one more way to add teardown code to specific test methods by registering one or more teardown blocks:

func testExample1() throws {
  // ...
  addTeardownBlock {
    // Test specific teardown
  }
}

Notes:

  • XCTest runs the teardown blocks after the test method but before calling the tearDown instance methods.
  • You can call async methods with await in the block and you can throw errors.
  • You can add teardown blocks in the setUp instance method but not from within a teardown method or another teardown block.
  • If you add more than one teardown block XCTest calls them in reverse order. It calls the last block registered first.
  • XCTest calls the blocks regardless of whether the test passes or fails. The teardown block is not called if the test crashes.

Order of Execution

To summarise, here’s the order that XCTest calls the various setup and teardown methods for a single test case class with multiple test cases:

// Run the tests in SessionManagerTests.swift
Test Suite 'SessionManagerTests' started

// Class setup
class func setUp()

// Run test case - testExample1
Test Case: textExample1 started

// test case setup
func setUp() async throws
func setUpWithError() throws
func setUp()

// Run the actual test method
func testExample1() throws

// Run any registered teardown blocks
teardown block 2
teardown block 1

// test case teardown
func tearDown()
func tearDownWithError() throws
func tearDown() async throws

// test case finished
Test Case: testExample1 passed

// Next test case
Test Case: textExample2 started
...
Test Case: testExample2 passed

// Class tearDown
class func tearDown()

// Test suite is finished
Test Suite 'SessionManagerTests' passed

Setup in setUp, Teardown in tearDown…

For a practical example let’s assume I’m writing tests for a session manager class:

final class SessionManager: NSObject { ... }

I want to create an instance of the session manager before I run each test and destroy it at the end of the test. I usually end up with something that looks like this:

final class SessionManagerTests: XCTestCase {
  var sessionManager: SessionManager!

  override func setUpWithError() throws {
    sessionManager = SessionManager()
  }

  override func tearDownWithError() throws {
    sessionManager = nil
  }

  func testSomeFeature() { ... }
}

We need a property to store the session manager under test. I’m creating the manager in the setUpWithError method so it ends up as an optional. Making it an implicitly unwrapped optional saves a lot of optional chaining.

It’s Not Obvious…

It can be tempting to get rid of that optional by directly initializing the property:

final class SessionManagerTests: XCTestCase {
  let sessionManager = SessionManager()
  ...
}

It’s not obvious but there’s a potential problem with that approach. XCTest creates an instance of our SessionManagerTests class for each test method. If I have five tests, XCTest creates five instances of SessionManagerTests each with its own session manager. That’s probably not what I want.

You can see what’s happening if I add a log message in the init method of the SessionManager class:

SessionManager:init
SessionManager:init
SessionManager:init
SessionManager:init
SessionManager:init
Test Suite 'SessionManagerTests' started at 2022-08-22 16:45:25.299
Test Case '-[XCTTests.SessionManagerTests testExample1]' started.

Only after XCTest has created five instances of the test suite each with its own session manager does it start to run the individual test cases. I’ve not found any official Apple documentation that explains how XCTest creates and runs tests. Let me know if you’ve found something. The best description I’ve found is by Jon Reid.

What Should You Do?

In summary, take the hint from the method names and create your objects in setUp and remove them in tearDown.

I’ve never needed to use the class setup/teardown methods or teardown blocks. If you have, I’m interested to hear your use case.