XCTest + Swift: SetUp/TearDown vs Factory Methods
In this episode, we dive into XCTestCase
’s lifecycle and show different ways for configuring your system under test.
XCTest, similarly to JUnit, invokes each test method on a separate instance of the test case. To illustrate with an example, imagine that you have ten test methods, the XCTest framework will instantiate ten instances of the test case class and invoke only one of the methods per instance. We believe this is a good design choice, as by running the test methods in separate instances we can avoid sharing state between tests. However, XCTestCase
’s behavior isn’t that obvious which can make it counterintuitive when compared with how we're used to using objects to share state between methods.
Swift Factory methods
Although the setUp/tearDown
configuration method is valid, at Essential Developer we noticed that moving configuration code to factory methods would yield better results in most cases.
Here are the main reasons why we prefer factory methods over the setUp/tearDown
configuration:
Many tests have a different setup/configuration, so there are no significant benefits in sharing the object instantiation.
We normally use constructor injection. If we create a class as a property in the class scope, we would have to use property injection to be able to configure the instance for each test. Which is fine too, but we prefer constructor injection since most of our classes don’t allow mutability.
We want to test the whole lifecycle of the system under test instance, to guarantee there are no issues when it's removed from memory. We could achieve this with the
setUp()
approach, but to test the whole lifecycle we would also need to set it tonil
in thetearDown()
. By creating the instance in the test method scope, we guarantee its lifecycle is tested within the method (since the instance will be freed automatically).It would take more lines of code to do the same thing and, in our opinion, it would negatively impact the design of the test and the production code (e.g.,
constructor->property
injection).setUp/tearDown
can make the tests harder to read/understand since, when reading the code, we would have to scroll/jump scope (from test method tosetUp/tearDown
code) to understand the whole context. We prefer to keep our test setup (Given/When/Then or Arrange/Act/Assert) in the shortest scope possible.
When we use XCTestCase setUp/tearDown in Swift
To be clear, we avoid setUp/tearDown
configuration because we believe there are better ways, but we do use it when it's useful to us.
For example, setUp/tearDown
can be useful when dealing with global state (e.g., databases) that needs to be restated for each test (as we don't want to pollute the test methods with those details).
Conclusion
We would like you to be proactive and think about the problems you're facing so you find the best solution for you. For example, our approach could be wrong for you, so be critical and let us know your preferred way.
Subscribe now to our Youtube channel and catch free new episodes every week.
We’ve been helping dedicated developers to get from low paying jobs to high tier roles – sometimes in a matter of weeks! To do so, we continuously run and share free market researches on how to improve your skills with Empathy, Integrity, and Economics in mind. If you want to step up in your career, access now our latest research for free.