London Escorts sunderland escorts 1v1.lol unblocked yohoho 76 https://www.symbaloo.com/mix/yohoho?lang=EN yohoho https://www.symbaloo.com/mix/agariounblockedpvp https://yohoho-io.app/ https://www.symbaloo.com/mix/agariounblockedschool1?lang=EN
11.9 C
New York
Tuesday, February 25, 2025

Testing completion handler primarily based code in Swift Testing – Donny Wals


Printed on: December 4, 2024

Swift’s new trendy testing framework is completely pushed by asynchronous code. Which means all of our take a look at capabilities are async and that we’ve to guarantee that we carry out all of our assertions “synchronously”.

This additionally signifies that completion handler-based code is just not as simple to check as code that leverages structured concurrency.

On this submit, we’ll discover two approaches that may be helpful whenever you’re testing code that makes use of callbacks or completion handlers in Swift Testing.

First, we’ll have a look at the built-in affirmation methodology from the Swift Testing framework and why it may not be what you want. After that, we’ll have a look at leveraging continuations in your unit checks to check completion handler primarily based code.

Testing async code with Swift Testing’s confirmations

I’ll begin this part by stating that the principle cause that I’m masking affirmation is that it’s current within the framework, and Apple suggests it as an possibility for testing async code. As you’ll study on this part, affirmation is an API that’s principally helpful in particular situations that, in my expertise, don’t occur all that always.

With that mentioned, let’s see what affirmation can do for us.

Typically you will write code that runs asynchronously and produces occasions over time.

For instance, you may need a little bit of code that performs work in numerous steps, and through that work, sure progress occasions must be despatched down an AsyncStream.

As traditional with unit testing, we’re not going to essentially care in regards to the actual particulars of our occasion supply mechanism.

In truth, I will present you ways that is executed with a closure as a substitute of an async for loop. Ultimately, the small print right here don’t matter. The principle factor that we’re concerned with proper now could be that we’ve a course of that runs and this course of has some mechanism to tell us of occasions whereas this course of is going on.

Listed here are among the guidelines that we need to take a look at:

  • Our object has an async methodology referred to as createFile that kicks of a course of that entails a number of steps. As soon as this methodology completes, the method is completed too.
  • The thing additionally has a property onStepCompleted that we will assign a closure to. This closure is known as for each accomplished step of our course of.

The onStepCompleted closure will obtain one argument; the finished step. This might be a worth of kind FileCreationStep:

enum FileCreationStep {
  case fileRegistered, uploadStarted, uploadCompleted
}

With out affirmation, we will write our unit take a look at for this as follows:

@Take a look at("File creation ought to undergo all three steps earlier than finishing")
func fileCreation() async throws {
  var completedSteps: [FileCreationStep] = []
  let supervisor = RemoteFileManager(onStepCompleted: { step in
    completedSteps.append(step)
  })

  attempt await supervisor.createFile()
  #count on(completedSteps == [.fileRegistered, .uploadStarted, .uploadCompleted])
}

We are able to additionally refactor this code and leverage Apple’s affirmation method to make our take a look at look as follows:

@Take a look at("File creation ought to undergo all three steps earlier than finishing")
func fileCreation() async throws {
  attempt await affirmation(expectedCount: 3) { verify in 
    var expectedSteps: [FileCreationStep] = [.fileRegistered, .uploadStarted, .uploadCompleted]

    let supervisor = RemoteFileManager(onStepCompleted: { step in
      #count on(expectedSteps.removeFirst() == step)
      verify()
    })

    attempt await supervisor.createFile()
  }
}

As I’ve mentioned within the introduction of this part; affirmation‘s advantages usually are not clear to me. However let’s go over what this code does…

We name affirmation and we offer an anticipated variety of instances we wish a affirmation occasion to happen.

Observe that we name the affirmation with attempt await.

Which means our take a look at is not going to full till the decision to our affirmation completes.

We additionally cross a closure to our affirmation name. This closure receives a verify object that we will name for each occasion that we obtain to sign an occasion has occurred.

On the finish of my affirmation closure I name attempt await supervisor.createFile(). This kicks off the method and in my onStepCompleted closure I confirm that I’ve acquired the fitting step, and I sign that we’ve acquired our occasion by calling verify.

Right here’s what’s fascinating about affirmation although…

We should name the verify object the anticipated variety of instances earlier than our closure returns.

Which means it’s not usable whenever you need to take a look at code that’s totally completion handler primarily based since that might imply that the closure returns earlier than you’ll be able to name your affirmation the anticipated variety of instances.

Right here’s an instance:

@Take a look at("File creation ought to undergo all three steps earlier than finishing")
func fileCreationCompletionHandler() async throws {
  await affirmation { verify in 
    let expectedSteps: [FileCreationStep] = [.fileRegistered, .uploadStarted, .uploadCompleted]
    var receivedSteps: [FileCreationStep] = []

    let supervisor = RemoteFileManager(onStepCompleted: { step in
      receivedSteps.append(step)
    })

    supervisor.createFile {
      #count on(receivedSteps == expectedSteps)
      verify()
    }
  }
}

Discover that I’m nonetheless awaiting my name to affirmation. As a substitute of 3 I cross no anticipated depend. Which means our verify ought to solely be referred to as as soon as.

Within the closure, I’m operating my completion handler primarily based name to createFile and in its completion handler I verify that we’ve acquired all anticipated steps after which I name verify() to sign that we’ve carried out our completion handler primarily based work.

Sadly, this take a look at is not going to work.

The closure returns earlier than the completion handler that I’ve handed to createFile has been referred to as. Which means we don’t name verify earlier than the affirmation’s closure returns, and that ends in a failing take a look at.

So, let’s check out how we will change this in order that we will take a look at our completion handler primarily based code in Swift Testing.

Testing completion handlers with continuations

Swift concurrency comes with a function referred to as continuations. In case you are not aware of them, I might extremely advocate that you just learn my submit the place I am going into how you need to use continuations. For the rest of this part, I will assume that you already know continuations fundamentals. I’ll simply have a look at how they work within the context of Swift testing.

The issue that we’re attempting to unravel is actually that we don’t want our take a look at operate to return till our completion handler primarily based code has totally executed. Within the earlier part, we noticed how utilizing a affirmation does not fairly work as a result of the affirmation closure returns earlier than the file managers create file finishes its work and calls its completion handler.

As a substitute of a affirmation, we will have our take a look at watch for a continuation. Within the continuation, we will name our completion handler primarily based APIs after which resume the continuation when our callback is known as and we all know that we have executed all of the work that we have to do. Let’s have a look at what that appears like in a take a look at.

@Take a look at("File creation ought to undergo all three steps earlier than finishing")
func fileCreationCompletionHandler() async throws {
  await withCheckedContinuation { continuation in
    let expectedSteps: [FileCreationStep] = [.fileRegistered, .uploadStarted, .uploadCompleted]
    var receivedSteps: [FileCreationStep] = []

    let supervisor = RemoteFileManager(onStepCompleted: { step in
      receivedSteps.append(step)
    })

    supervisor.createFile {
      #count on(receivedSteps == expectedSteps)
      continuation.resume(returning: ())
    }
  }
}

This take a look at appears to be like similar to the take a look at that you just noticed earlier than, however as a substitute of ready for a affirmation, we’re now calling the withCheckedContinuation operate. Within the closure that we handed to that operate, we carry out the very same work that we carried out earlier than.

Nonetheless, within the createFile operate’s completion handler, we resume the continuation solely after we have made positive that the acquired steps from our onStepCompleted closure match with the steps to be anticipated.

So we’re nonetheless testing the very same factor, however this time our take a look at is definitely going to work. That is as a result of the continuation will droop our take a look at till we resume the continuation.

Whenever you’re testing completion handler primarily based code, I often discover that I’ll attain for this as a substitute of reaching for a affirmation as a result of a affirmation doesn’t work for code that doesn’t have one thing to await.

In Abstract

On this submit, we explored the variations between continuations and confirmations for testing asynchronous code.

You have discovered that Apple’s advisable method for testing closure primarily based asynchronous code is with confirmations. Nonetheless, on this submit, we noticed that we’ve to name our verify object earlier than the affirmation closure returns, in order that signifies that we have to have one thing asynchronous that we await for, which is not at all times the case.

Then I confirmed you that if you wish to take a look at a extra conventional completion handler primarily based API, which might be what you are going to be doing, you need to be utilizing continuations as a result of these enable our checks to droop.

We are able to resume a continuation when the asynchronous work that we had been ready for is accomplished and we’ve asserted the outcomes of our asynchronous work are what we’d like them to be utilizing the #count on or #require macros.

Related Articles

Social Media Auto Publish Powered By : XYZScripts.com