Better Test Dependencies: Immediacy

Episode #140 • Mar 29, 2021 • Subscriber-Only

A major source of complexity in our applications is asynchrony. It is a side effect that is easy to overlook and can make testing more difficult and less reliable. We will explore the problem and come to a solution using Combine schedulers.

Previous episode
Better Test Dependencies: Immediacy
Next episode
Locked

Unlock This Episode

Our Free plan includes 1 subscriber-only episode of your choice, plus weekly updates from our newsletter.

Sign in with GitHub

Introduction

So this is pretty amazing. We are now getting a ton of insight into our code base by embracing exhaustive dependencies, and in particular invoking XCTFail immediately for any dependencies that we do not expect to be called. This allows us to be instantly notified when one of our features starts accessing a dependency we don’t expect, and on the flip side allows us to introduce new dependencies to our feature and be instantly notified of which tests need to be updated.

But there’s still more to discover. Failing dependencies greatly improved the developer experience when writing tests, but there is still room for more improvement. When we write tests that deal with time, such as delaying or debouncing, we like to use a test scheduler because it allows us to deterministically control the flow of time. We even have a todo test that specifically asserts that when you complete a todo, wait half a second, then complete another todo, and then wait a full second, that the todos were not sorted until the full one and a half seconds passed. It could actually capture that intermediate moment where the second todo’s completion cancelled the sorting effect. And that’s incredibly powerful.

However, sometimes we deal with schedulers that do not involve the passage of time. They are just used to execute on specific queues, such as when you use the .subscribe(on:) or .receive(on:) operator. If we use the test scheduler for these situations we have to litter our tests with scheduler.advance() calls in order to push them a tick forward and execute their work. Sometimes you do really want that, like if you want to test some synchronous effects that run before an asynchronous effect. However, most of the times it’s an unnecessary annoyance, and we can definitely improve it.

Even better, by addressing this test annoyance we’ll actually unlock something really cool for SwiftUI previews. We’ll show how we can exercise more of our feature’s logic using static previews when typically you would have to resort to running the live preview.

Let’s start by demonstrating the problem that test schedulers can cause. We are going to resurrect the project we built for our “Designing Dependencies” series of episodes. In those episodes we built a moderately complex application that made use of an API client, a location manager, and a network monitor in order to implement a simple weather app. Let’s recap:

Designing dependencies: recap and problem


References

Downloads

Get started with our free plan

Our free plan includes 1 subscriber-only episode of your choice, access to 62 free episodes with transcripts and code samples, and weekly updates from our newsletter.

View plans and pricing