October 23, 2020

Progress Expectations

Expectations are pretty common when you're writing unit tests. Generally an expectation represents an event you want to wait for in your test. You can tell the test to wait until that expectation is fulfilled.

func testSomethingWorks() {
  let itHappened = expectation(description: "The event should happen")
  
  let testSubject = TestSubject()
  testSubject.makeItHappen {
    itHappened.fulfill()
  }

  waitForExpectations(timeout: 1.0)
}

Tests for iOS and Mac generally look like this. You setup an expectation, setup whatever part of your code you're trying to test that does something asynchronous, and then tell the test to wait until the expectation is fulfilled. This is probably the most common use case, so you'll end up seeing it often.

Sometimes this is not enough and we need to dig for other ways to fulfill expectations. One I found especially handy was with keyValueObservingExpectation(for:keyPath:handler:). This function creates an exception that is fulfilled based on observed values from your test subject. I use it to make expectations that fulfill based on Progress objects.

func progressExpectation(_ progress: Progress) {
  keyValueObservingExpectation(for: progress, keyPath: "completedUnitCount") { (value, info) -> Bool in
    let current = value as! Progress
    return current.totalUnitCount == current.completedUnitCount
  }
}

This way I can make tests wait for operations to complete even if they don't have completion handlers I can use to fulfill the expectation. You could also use something like this to wait for partial completion of operations rather than total completion. A handy thing to keep in mind for your next tests!