Using Stubs in Testing with Async/Await

abdul ahad
3 min readJan 9, 2025

--

Photo by HackerNoon on Unsplash

If you haven’t already, feel free to understand Stubs in detail in my article on stubs.

In this article, we’ll go through an example whereby we explore how to use stubs with async/await in Swift. I use the same example that i used in the article i shared above however, that was with completion closures and this is with async/await

Example: HTTP Client Stub with Async/Await

The Scenario

Imagine you have an HTTPClient protocol for performing network requests asynchronously. To test a service dependent on this client, we’ll use a stub to control the response.

Step 1: Define the HTTPClient Protocol

protocol HTTPClient {
func get(from url: URL) async throws -> Data
}

This protocol defines a single asynchronous method, get(from:), which performs a GET request and either returns Data or throws an error.

Step 2: Create the HTTPClient Stub

The HTTPClientStub conforms to the HTTPClient protocol and allows you to define a controlled response using async/await.

class HTTPClientStub: HTTPClient {
var stubbedResponse: Result<Data, Error>

func get(from url: URL) async throws -> Data {
switch stubbedResponse {
case .success(let data):
return data
case .failure(let error):
throw error
}
}
}

Here, the stubbedResponse property holds the predefined result (either success or failure) returned when the get(from:) method is called.

Step 3: Configure the Stubbed Response

You can configure the stub with specific responses for your tests:

  1. Simulating a Successful Response
let httpClientStub = HTTPClientStub(stubbedResponse: .success(Data("Expected data".utf8)))
  1. Simulating an Error Response
let httpClientStub = HTTPClientStub(stubbedResponse: .failure(NSError(domain: "Test", code: 1, userInfo: nil)))

Using the Stub in Tests

Let’s use the HTTPClientStub in a test for a service that relies on asynchronous HTTP requests.

Example Test

Suppose you have a RemoteLoader that fetches data using the HTTPClient:

class RemoteLoader {
private let client: HTTPClient

init(client: HTTPClient) {
self.client = client
}
func load(from url: URL) async throws -> Data {
return try await client.get(from: url)
}
}

You can test the RemoteLoader using the stub.

  1. Test for a Successful Response
func testLoad_SuccessfulResponse_ReturnsData() async throws {
// Arrange
let expectedData = Data("Expected data".utf8)
let stub = HTTPClientStub(stubbedResponse: .success(expectedData))
let loader = RemoteLoader(client: stub)
let url = URL(string: "https://example.com")!

// Act
let receivedData = try await loader.load(from: url)
// Assert
XCTAssertEqual(receivedData, expectedData, "The loader should return the stubbed data.")
}
  1. Test for an Error Response
func testLoad_FailureResponse_ThrowsError() async throws {
// Arrange
let expectedError = NSError(domain: "Test", code: 1, userInfo: nil)
let stub = HTTPClientStub(stubbedResponse: .failure(expectedError))
let loader = RemoteLoader(client: stub)
let url = URL(string: "https://example.com")!

// Act & Assert
do {
_ = try await loader.load(from: url)
XCTFail("Expected an error but got success")
} catch {
XCTAssertEqual(error as NSError, expectedError, "The loader should throw the stubbed error.")
}
}

Benefits of Using Stubs with Async/Await

  1. Controlled Behavior: Stubs allow you to simulate both success and failure scenarios for asynchronous dependencies.
  2. Faster Tests: Avoid slow or unpredictable real API calls by simulating responses locally.
  3. Focused Testing: Isolate the component being tested without interference from external systems.
  4. Ease of Use: With async/await, handling stubs in tests is simpler and more readable compared to callback-based approaches.

Conclusion

Stubs are an essential part of writing effective tests, especially in the context of asynchronous programming. By simulating predefined responses, they enable you to isolate and verify the behavior of your code under controlled conditions. In the example above, the HTTPClientStub demonstrates how stubs can be used with async/await to test components that depend on asynchronous operations. With proper use of stubs, you can write tests that are reliable, fast, and easy to understand.

References:

--

--

abdul ahad
abdul ahad

Written by abdul ahad

A software developer dreaming to reach the top and also passionate about sports and language learning

No responses yet