Home > other >  Understanding XCTestCase life cycle - How to perform setup BEFORE Xcode unit test launches the app?
Understanding XCTestCase life cycle - How to perform setup BEFORE Xcode unit test launches the app?

Time:10-07

I am new to unit tests in Xcode and Swift and have some trouble to understand the life cycle of XCTestCase.

How/where to add setup code which is executed before the actual app is launched?

Problem is, that first the host app is launched before any of the test setup methods (class func setUp(), func setUp(), func setUpWithError()) are executed.

It is even possible to run test code before the host app launches?


Details:

As described in a previous question my app uses a SQLite database to persist some data. When the app launches a database connection is created and data is read from the database.

To make tests consistent and repeatable I would like to use a fresh database with some well defined data every time a run the test. To archive this I tried to override setUpWithError remove the existing db file and move a file with pre-defined data in place instead.

Unfortunately this does not work, because setUpWithError is executed only after the host app was launched. The same is true for all other test setup methods.

Moving a fresh database file in place before running the tests is only an example. The problem is the same for all local data which should be in place before the host app launches to ensure repeatable tests.

An answer to my previous question included a UIApplication extension with a isTesting method which can be used to check if a test is performed. While I could use this in my app code to setup the test data, I would consider this a bad solution. I would like to keep the code completely separated from the production code. Is this possible?

CodePudding user response:

There are several approaches to set up data before running a test case

NSPrincipalClass

As described here you can create a class, and the init method of that class is executed before running any test. This helps setting up dependencies used by many test cases. I don't think this is the way to go in your case.

isTesting

Instead of setting up the code in your app target, you can also check for isTesting early in the didFinishLaunchingWithOptions method of your AppDelegate and simply return there. In this case the regular code is not executed and you can run you custom database setup code in the setup of your test class.

Dependency Injection

Right now you are doing integration testing in my opinion. If you want to have proper unit tests, they should not operate against the database of the underlying app. Instead create an extra (in memory) database and inject that into the code you are testing. I think this is the way you should follow. The benefits are

  • Your app is not affected by your tests. Next time you start the app to manually test, your original data is still there.
  • Your unit tests are not affected by manual changes to the app
  • Your unit tests are also independent of each other. Order of execution doesn't change the results when setting up the database fresh for each test case.

CodePudding user response:

It is even possible to run test code before the host app launches

If you have to ask that, your tests are not unit tests. Unit tests test code, not the app. A test that requires the app to be running would be a UI test.

In fact, the best approach for unit tests is to put all your testable code into a framework and give the framework unit tests; that way, your tests run much faster because the app never has to launch at all.

It sounds to me like the problem lies deeper in your app's code: you have evidently not written your code in such a way as to be testable. So that would be your first move. Writing testable code, and writing unit tests, is an art; you have to separate out the "system under test", which should be your code alone, and make sure you are not testing anything that doesn't belong to you and whose workings are already known — like Core Data.

  • Related