11.2 C
Canberra
Saturday, October 25, 2025

Testing completion handler APIs with Swift Testing – Donny Wals


Printed on: October 16, 2024

The Swift testing framework is an extremely useful gizmo that enables us to put in writing extra expressive exams with handy and trendy APIs.

That is my first publish about Swift Testing, and I’m primarily writing it as a result of I wished to put in writing about one thing that I encountered not too way back once I tried to make use of Swift testing on a code base the place I had each async code in addition to older completion handler primarily based code.

The async code was very simple to check as a result of how Swift Testing is designed, and I might be writing extra about that sooner or later.

The completion handler base code was a little bit bit tougher to check, primarily as a result of I used to be changing my code from XCTest with check expectations to regardless of the equal can be in Swift testing.

Understanding the issue

After I began studying Swift testing, I really checked out Apple’s migration doc and I discovered that there’s an one thing that’s purported to be analogous to the expectation object, which is the affirmation object. The examples from Apple have one little caveat in there.

The Swift Testing instance appears to be like a little bit bit like this:

// After
struct FoodTruckTests {
  @Check func truckEvents() async {
    await affirmation("…") { soldFood in
      FoodTruck.shared.eventHandler = { occasion in
        if case .soldFood = occasion {
          soldFood()
        }
      }
      await Buyer().purchase(.soup)
    }
    ...
  }
  ...
}

Now, as you’ll be able to see within the code above, the instance that Apple has reveals that we have now a perform and a name to the affirmation perform in there, which is how we’re supposed to check our async code.

They name their outdated completion handler primarily based API and within the occasion handler closure they name their affirmation closure (referred to as soldFood within the instance).

After calling setting the occasion handler they await Buyer().purchase(.soup).

And that is actually the place Apple desires us to pay shut consideration as a result of within the migration doc, they point out that we wish to catch an occasion that occurs throughout some asynchronous course of.

The await that they’ve as the ultimate line of that affirmation closure is absolutely the important thing a part of how we ought to be utilizing affirmation.

After I tried emigrate my completion handler primarily based code that I examined with XCTestExpectation, I did not have something to await. My unique testing code regarded a little bit bit like this:

func test_valueChangedClosure() {
  let anticipate = expectation(description: "Anticipated synchronizer to finish")

  let synchronizer = Synchronizer()
  synchronizer.onComplete = {
    XCTAssert(synchronizer.newsItems.rely == 2)
    anticipate.fulfill()
  }

  synchronizer.synchronize()
  waitForExpectations(timeout: 1)
}

Primarily based on the migration information and skimming the examples I although that the next code can be the Swift Testing equal:

@Check func valueChangedClosure() async {    
  await affirmation("Synchronizer completes") { @MainActor affirm in
    synchronizer.onComplete = {
      #anticipate(synchronizer.newsItems.rely == 2)
      affirm()
    }

    synchronizer.synchronize()
  }
}

My code ended up wanting fairly just like Apple’s code however the important thing distinction is the final line in my affirmation. I’m not awaiting something.

The outcome when working that is all the time a failing check. The check just isn’t ready for me to name the affirm closure in any respect. That await proper on the finish in Apple’s pattern code is just about wanted for this API to be usable as a alternative of your expectations.

What Apple says within the migration information while you fastidiously learn is definitely that all the confirmations need to be referred to as earlier than your closure returns:

Confirmations perform equally to the expectations API of XCTest,
nevertheless, they don’t block or droop the caller whereas ready for a
situation to be fulfilled. As an alternative, the requirement is predicted to be confirmed (the equal of fulfilling an expectation) earlier than affirmation() returns

So every time that affirmation closure returns, Swift Testing expects that we have now confirmed all of our confirmations. In a standard completion handler-based setup, this would possibly not be the case since you’re not awaiting something as a result of you do not have something to await.

This was fairly tough to determine.

Write a check for completion handler code

The answer right here is to not use a affirmation object right here as a result of what I assumed would occur, is that the affirmation would act a little bit bit like a continuation within the sense that the Swift check would look ahead to me to name that affirmation.

This isn’t the case.

So what I’ve actually discovered is that one of the best ways to check your completion handler-based APIs is to make use of continuations.

You need to use a continuation to wrap your name to the completion handler-based API after which within the completion handler, do all your assertions and resume your continuation. This can then resume your check and it’ll full your check.

Right here’s what that appears like for instance:

@Check func valueChangedClosure() async {
    await withCheckedContinuation { continuation in
        synchronizer.onComplete = {
            #anticipate(synchronizer.newsItems.rely == 2)
            continuation.resume()
        }

        synchronizer.synchronize()
    }
}

This strategy works very nicely for what I wanted, and it permits me to droop the check whereas my callback primarily based code is working.

It is the best strategy I may give you, which is often a very good signal. However if in case you have another approaches that you simply desire, I’d love to listen to about them, particularly when it pertains to testing completion handler APIs. I do know this isn’t a full-on alternative for every part that we are able to do with expectations, however for the completion handler case, I believe it is a fairly good alternative.

When to not use continuations for testing completion handler code

The strategy of testing outlined above assumes that our code is considerably freed from sure bugs the place the completion handler is rarely referred to as. Our continuation does not do something to stop our check from hanging perpetually which may (let’s be trustworthy, will) be a difficulty for sure eventualities.

There are code snippets on the market that can get you the power to deal with timeouts, just like the one discovered on this gist that was shared with me by Alejandro Ramirez.

I have not accomplished intensive testing with this snippet but however a few preliminary exams look good to me.

Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here

[td_block_social_counter facebook="tagdiv" twitter="tagdivofficial" youtube="tagdiv" style="style8 td-social-boxed td-social-font-icons" tdc_css="eyJhbGwiOnsibWFyZ2luLWJvdHRvbSI6IjM4IiwiZGlzcGxheSI6IiJ9LCJwb3J0cmFpdCI6eyJtYXJnaW4tYm90dG9tIjoiMzAiLCJkaXNwbGF5IjoiIn0sInBvcnRyYWl0X21heF93aWR0aCI6MTAxOCwicG9ydHJhaXRfbWluX3dpZHRoIjo3Njh9" custom_title="Stay Connected" block_template_id="td_block_template_8" f_header_font_family="712" f_header_font_transform="uppercase" f_header_font_weight="500" f_header_font_size="17" border_color="#dd3333"]
- Advertisement -spot_img

Latest Articles