Collection
Unlock This Episode
Our Free plan includes 1 subscriber-only episode of your choice, plus weekly updates from our newsletter.
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:
Subscribe to Point-Free
Access this episode, plus all past and future episodes when you become a subscriber.
Already a subscriber? Log in
Exercises
Define immediate schedulers for
OperationQueue
andRunLoop
.Solution
This can be done exactly the same as we did for
failing
schedulers last week!extension Scheduler where SchedulerTimeType == OperationQueue.SchedulerTimeType, SchedulerOptions == OperationQueue.SchedulerOptions { public static var immediate: AnySchedulerOf<Self> { .immediate(now: .init(Date())) } } extension Scheduler where SchedulerTimeType == RunLoop.SchedulerTimeType, SchedulerOptions == RunLoop.SchedulerOptions { public static var immediate: AnySchedulerOf<Self> { .immediate(now: .init(Date())) } }
Implement cancellation in the designing dependencies application. This could be as simple as adding a button to the view. Feel free to get creative!
With cancellation implemented in the UI, it’d be nice to be able to see it working in the preview. To do so, introduce a
badWifi
weather client, which slows each of its endpoints down by several seconds.What happens to the UI if we cancel things before the first API request to fetch locations succeeds? What happens to the UI if we cancel things after it succeeds? How might we improve the user experience?
References
Collection: Schedulers
Brandon Williams & Stephen Celis • Thursday Jun 4, 2020There’s a lot of great material in the community covering almost every aspect of the Combine framework, but sadly Combine’s
Scheduler
protocol hasn’t gotten much attention. It’s a pretty mysterious protocol, and Apple does not provide much documentation about it, but it is incredibly powerful and can allow one to test how time flows through complex publishers.
Designing Dependencies
Brandon Williams & Stephen Celis • Monday Jul 27, 2020We develop the idea of dependencies from the ground up in this collection of episodes:
Let’s take a moment to properly define what a dependency is and understand why they add so much complexity to our code. We will begin building a moderately complex application with three dependencies, and see how it complicates development, and what we can do about it.
Composable Architecture: Dependency Management
Brandon Williams & Stephen Celis • Monday Feb 17, 2020We made dependencies a first class concern of the Composable Architecture by baking the notion of dependencies directly into the definition of its atomic unit: the reducer.
Composable Architecture
Brandon Williams & Stephen Celis • Monday May 4, 2020The Composable Architecture is a library for building applications in a consistent and understandable way, with composition, testing and ergonomics in mind.