So, this is pretty amazing. After diving deep into the Clock
protocol so that we could understand what core concept it represents, and seeing how it differs from Combine schedulers, we showed how to make use of clocks in our features. There were a number of false starts to do that, first needing to wrap our minds around clock existentials and primary associated types and then coming to grips with some missing APIs in the standard library, but once we got out of the weeds we had the ability to swap clocks in and out of our feature code. We could use a continuous clock in the feature when running on a device, but sub out for a more controllable clock in tests and SwiftUI previews.
That led us to the creation of the “immediate” clock and the “unimplemented” clock. Both are powerful, and allow you to make your tests stronger and make it so that you don’t have to literally wait for time to pass in order to see how your feature behaves.
But there’s one more kind of clock that we want to have for our tests, and it’s even more powerful than any of the other clocks we have discussed. While the immediate clock allows us to squash all of time into a single instant, that isn’t always what we want. Sometimes we want to actually control the flow of time in our feature so that we can see how multiple time-based asynchronous tasks interleave their execution, or so that we can wiggle ourselves in between time-based tasks to see what is happening between events.
This is something we explored quite a bit in our series of episodes on Combine schedulers. When writing highly reactive code that needs to leverage time-based operators, such as delays, debounces and throttles, it becomes very important to have a scheduler for which you can explicitly control the flow of time.
So, let’s see why exactly we want this tool, and what it takes to implement it.