iosswiftintegration-testingrunloop

Why is runloop called when testing


I usually see RunLoop.current.run(until: Date()) called in integration tests.

For example in this article and in this open source project.

The explanation given in the article is

The RunLoop.current.run(until: Date()) statement makes sure the run loop associated with the current thread has ample time to let the drawing operations to complete

If that is true why wouldn't we give it more time, how do we know that Date() is enough?

I read a few articles about the run loop and it seems to me like the reason that line of code is added is to start the app. It seems like the appDelegate usually automatically triggers or starts the run loop, but since we are testing we need to trigger the run loop ourselves.

I might be missing some fundamental understand of threads or run loops but I hope that someone can give some insight.


Solution

  • If that is true why wouldn't we give it more time, how do we know that Date() is enough?

    We know from experimentation, I suppose. A more “correct” way to do it would be:

    1. Install a run loop observer that somehow detects when the views have been laid out and drawn.
    2. Run the run loop indefinitely (until Date.distantFuture).
    3. In the observer installed in step 1, call CFRunLoopStop(CFRunLoopGetMain()) when it detects that the views have been laid out and drawn.

    However, that's a lot of extra work to do instead of just RunLoop.current.run(until: Date()), when the simpler method works and is unlikely to break.

    I read a few articles about the run loop and it seems to me like the reason that line of code is added is to start the app. It seems like the appDelegate usually automatically triggers or starts the run loop, but since we are testing we need to trigger the run loop ourselves.

    No, the app delegate does not start the run loop. The function UIApplicationMain sets up the app and starts the run loop. If you create a new Objective-C project, you will find that the main function in main.m calls UIApplicationMain. In a typical Swift project, the @UIApplicationMain attribute attached to the app delegate tells the Swift compiler to generate equivalent code (a main function that calls UIApplicationMain) in AppDelegate.o.

    I might be missing some fundamental understand of threads or run loops but I hope that someone can give some insight.

    An app spends most of its life in the run loop, which (simplified) has these phases:

    1. Wait for an event. There are lots of event sources, including app startup, touches, timers, accelerometer, GPS, going to the background, returning to the foreground, and more.
    2. Handle the event, often by calling code written by you.
    3. If any views have the needsLayout property set, lay out the appropriate sections of the view hierarchy (which includes sending layoutSubviews messages).
    4. If any views have the needsDisplay property set, draw those views (by sending drawRect: messages).
    5. Go back to step 1.