Better Test Dependencies: Failability

Episode #139 • Mar 22, 2021 • Subscriber-Only

Exhaustively describing dependencies in your tests makes them stronger and easier to understand. We improve the ergonomics of this technique by ditching the fatalError in unimplemented dependencies, using XCTFail, and we open source a library along the way.

Failability
Introduction
00:05
Failing UUIDs
01:50
Failing schedulers
13:49
Tracing failability
21:07
Next time: immediacy
33:37

Unlock This Episode

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

Introduction

The fact that the test suite passes proves that there are only two tests of the entire suite that require analytics: when clearing todos and deleting todos. If we were to go in and start instrumenting more parts of our application we would instantly get feedback on which tests need to be updated. We wouldn’t need to hunt them down or audit our entire test suite to see where we should be further asserting for analytics events.

This means that if you come to this code 6 months from now in order to add some more analytics, you wouldn’t even have to think about what tests need to be updated. You could just run the entire suite and see which test cases get stuck on the unimplemented client. That’s the power of being more explicit and exhaustive with what dependencies your test cases are actually using.

However, there is one not very optimal thing about what we have done so far, and that’s the fact that when an unimplemented dependency is used it crashes the whole test suite. No other test will run, and that’s going to be really annoying in practice. If we have a long test suite then it takes just a single failure to stop the entire suite, and we’ll have no idea of what other tests failed until we fix the first one that failed.

So having the unimplemented dependencies was a nice way to get our feet wet with the concept of exhaustively describing our dependencies, but can we do better? Yes we can, but it comes with a few new complications that have to be worked out.

What if instead of doing a fatalError inside each endpoint of our dependency we put in a XCTFail? That would make our test fail, while also letting the rest of the suite run.

Let’s try it out with our simplest dependency, the UUID initializer.

This episode is for subscribers only.

Subscribe to Point-Free

Access this episode, plus all past and future episodes when you become a subscriber.

See plans and pricing

Already a subscriber? Log in

Exercises

  1. Define failing schedulers for OperationQueue and RunLoop.

    Solution

    Both operation queues and run loops have time types that wrap Date:

    extension Scheduler
    where
      SchedulerTimeType == OperationQueue.SchedulerTimeType,
      SchedulerOptions == OperationQueue.SchedulerOptions
    {
      public static var failing: AnySchedulerOf<Self> {
        .failing(
          now: .init(Date())
        )
      }
    }
    
    extension Scheduler
    where
      SchedulerTimeType == RunLoop.SchedulerTimeType,
      SchedulerOptions == RunLoop.SchedulerOptions
    {
      public static var failing: AnySchedulerOf<Self> {
        .failing(
          now: .init(Date())
        )
      }
    }
    

References

Better Testing Bonanza

Brandon Williams & Stephen Celis • Monday Mar 22, 2021

We open sourced a library for dynamically loading XCTFail so that you can ship test support code right along side production code. We also released new versions of Composable Architecture and Combine Schedulers that take advantage of the dynamic XCTFail to ship failing effects and schedulers so that you can make your tests more exhaustive. Check out the details in this blog post.

Designing Dependencies

Brandon Williams & Stephen Celis • Monday Jul 27, 2020

We 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.

A Tour of the Composable Architecture

Brandon Williams & Stephen Celis • Monday May 4, 2020

When we open sourced the Composable Architecture we released a 4-part series of episodes to show how to build a moderately complex application from scratch with it. We covered state management, complex effects, testing and more.

Composable Architecture: Dependency Management

Brandon Williams & Stephen Celis • Monday Feb 17, 2020

We 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, 2020

The Composable Architecture is a library for building applications in a consistent and understandable way, with composition, testing and ergonomics in mind.