Testing Timer Based Features in Swift

Isabela Karen Louli
3 min readApr 1, 2019

Hi Everyone, I developed a digital clock and I had to find a nice way to make it testable and of course to test it.

I’ve found 2 ways to do that:

1. Using expectations (async).

2. Creating a mocked timer object to enable synchronous test.

The following parts will describe this 2 ways.

The Starks are always right eventually: Tests are coming.

Getting Started

First, make sure to receive the Timer.Type as a dependency of your class, so you can use it to create a mock later.

init(timerProvider: Timer.Type = Timer.self) {
self.timerProvider = timerProvider
...
}

By using the timer type as a dependency of the class, we acquire the ability to control when it’ll call the schedule block.

To be able to mock the behaviour of the timer, we need to store it as a type and not an instance. Due to that we need to use Timer.Type in order to store a reference to the metatype.

If you are not familiar with metatypes, I’m sure you can find some article online, just search for Swift metatypes.

To schedule the timer, just use the timerProvider instead of Timer.

timerProvider.scheduledTimer(withTimeInterval: 1, repeats: true, block: { [weak self] timer in { 
// do some magic
...
}

How we can test it?

Expectations (async)

We can test using expectations, they provide a way to tell the test runner that you want to wait for a while, without blocking the thread, before proceeding.

The issue with that is for example: Let’s say the timer runs every 5 minutes, this’ll make the expectation waits for 5 minutes before continue, that makes your unit test get blocked during the waiting period.

This makes the test not viable, since unit tests should run constantly and such a huge blocker is not acceptable in most situations.

A Better Approach (sync)

First let’s create a mock class, that inherit from Timer, in the test project.

  1. As the scheduledTimer function is static, we need to somehow hold a reference to the timer block. One way of doing this, is create a static instance of the MockTimer itself.
  2. Here is where the schedule block is executed. Override the fire() function, or create your own function to execute the block.
  3. In order to make the mock works, we need to override the scheduledTimer function, since this is the function your controller class depends on. The most important thing here is to make sure to assign the block to the static instance of the timer.

Now that all the magic is done, we can just write the unit test:

func test_onTimerTick_shouldDoSomethingMagical() {
// 1
let controller = DigitalWatchController(timerProvider: MockTimer.self)
// 2
MockTimer.currentTimer.fire()

// 3
XCTAssertTrue(didSomethingMagical)
}
  1. Instantiate your class using the MockTimer.
  2. By calling the fire function here, it’ll synchronously execute the schedule block in the MockTimer class, allowing you to avoid async expectations.
  3. After the step 2 you’re good to assert the result of the timer block.

It seems a lot of code to just write a simple unit test, but it brings a huge benefit of not having to wait for async code execution.

That’s it!

What do you think? Feel free to comment if you have any questions, suggestions or feedback.

Thanks for reading!

--

--