Mocking a community connection in your Swift Checks – Donny Wals

Mocking a community connection in your Swift Checks – Donny Wals


Unit assessments must be as freed from exterior dependencies as attainable. Which means that you wish to have full management over every thing that occurs in your assessments.

For instance, in the event you’re working with a database, you need the database to be empty or in some predefined state earlier than your check begins. You use on the database throughout your check and after your check the database will be thrown away.

By making your assessments not rely on exterior state, you be sure that your assessments are repeatable, can run in parallel and do not rely on one check working earlier than one other check.

Traditionally, one thing just like the community is especially laborious to make use of in assessments as a result of what in case your check runs however you do not have a community connection, or what in case your check runs throughout a time the place the server that you just’re speaking to has an outage? Your assessments would now fail despite the fact that there’s nothing fallacious together with your code. So that you wish to decouple your assessments from the community in order that your assessments grow to be repeatable, impartial and run with out counting on some exterior server.

On this submit, I will discover two totally different choices with you.

One choice is to easily mock out the networking layer solely. The opposite choice makes use of one thing known as URLProtocol which permits us to take full management over the requests and responses inside URLSession, which suggests we will truly make our assessments work and not using a community connection and with out eradicating URLSession from our assessments.

Defining the code that we wish to check

With the intention to correctly work out how we’ll check our code, we must always most likely outline the objects that we wish to check. On this case, I wish to check a fairly easy view mannequin and networking pair.

So let’s check out the view mannequin first. Here is the code that I wish to check for my view mannequin.

@Observable
class FeedViewModel {
  var feedState: FeedState = .notLoaded
  non-public let community: NetworkClient

  init(community: NetworkClient) {
    self.community = community
  }

  func fetchPosts() async {
    feedState = .loading
    do {
      let posts = strive await community.fetchPosts()
      feedState = .loaded(posts)
    } catch {
      feedState = .error(error)
    }
  }

  func createPost(withContents contents: String) async throws -> Submit {
    return strive await community.createPost(withContents: contents)
  }
}

In essence, the assessments that I wish to write right here would verify that calling fetchPost would truly replace my listing of posts as new posts grow to be accessible.

Planning the assessments

I’d most likely name fetchPost to be sure that the feed state turns into a price that I count on, then I’d name it once more and return totally different posts from the community, ensuring that my feed state updates accordingly. I’d most likely additionally wish to check that if any error could be thrown in the course of the fetching section, that my feed state will grow to be the corresponding error sort.

So to boil that right down to a listing, here is the check I’d write:

  • Ensure that I can fetch posts
  • Ensure that posts get up to date if the community returns new posts
  • Ensure that errors are dealt with accurately

I even have the create submit operate, which is somewhat bit shorter. It would not change the feed state.

What I’d check there may be that if I create a submit with sure contents, a submit with the supplied contents is definitely what’s returned from this operate.

I’ve already carried out the networking layer for this view mannequin, so here is what that appears like.

class NetworkClient {
  let urlSession: URLSession
  let baseURL: URL = URL(string: "https://practicalios.dev/")!

  init(urlSession: URLSession) {
    self.urlSession = urlSession
  }

  func fetchPosts() async throws -> [Post] {
    let url = baseURL.appending(path: "posts")
    let (knowledge, _) = strive await urlSession.knowledge(from: url)

    return strive JSONDecoder().decode([Post].self, from: knowledge)
  }

  func createPost(withContents contents: String) async throws -> Submit {
    let url = baseURL.appending(path: "create-post")
    var request = URLRequest(url: url)
    request.httpMethod = "POST"
    let physique = ["contents": contents]
    request.httpBody = strive JSONEncoder().encode(physique)

    let (knowledge, _) = strive await urlSession.knowledge(for: request)

    return strive JSONDecoder().decode(Submit.self, from: knowledge)
  }
}

In a really perfect world, I’d be capable to check that calling fetchPosts on my community consumer is definitely going to assemble the proper URL and that it’ll use that URL to make a name to URLSession. Equally for createPost, I’d wish to be sure that the HTTP physique that I assemble is legitimate and accommodates the info that I intend to ship to the server.

There are primarily two issues that we might wish to check right here:

  1. The view mannequin, ensuring that it calls the proper capabilities of the community.
  2. The networking consumer, ensuring that it makes the proper calls to the server.

Changing your networking layer with a mock for testing

A typical approach to check code that depends on a community is to easily take away the networking portion of it altogether. As a substitute of relying on concrete networking objects, we’d rely on protocols.

Abstracting our dependencies with protocols

Here is what that appears like if we apply this to our view mannequin.

protocol Networking {
  func fetchPosts() async throws -> [Post]
  func createPost(withContents contents: String) async throws -> Submit
}

@Observable
class FeedViewModel {
  var feedState: FeedState = .notLoaded
  non-public let community: any Networking

  init(community: any Networking) {
    self.community = community
  }

  // capabilities are unchanged
}

The important thing factor that modified right here is that as a substitute of relying on a community consumer, we rely on the Networking protocol. The Networking protocol defines which capabilities we will name and what the return varieties for these capabilities can be.

Because the capabilities that we have outlined are already outlined on NetworkClient, we will replace our NetworkClient to evolve to Networking.

class NetworkClient: Networking {
  // No adjustments to the implementation
}

In our software code, we will just about use this community consumer passage to our feed view mannequin and nothing would actually change. It is a actually low-key approach to introduce testability into our codebase for the feed view mannequin.

Mocking the community in a check

Now let’s go forward and write a check that units up our feed view mannequin in order that we will begin testing it.

class MockNetworkClient: Networking {
  func fetchPosts() async throws -> [Post] {
    return []
  }

  func createPost(withContents contents: String) async throws -> Submit {
    return Submit(id: UUID(), contents: contents)
  }
}

struct FeedViewModelTests {
  @Check func testFetchPosts() async throws {
    let viewModel = FeedViewModel(community: MockNetworkClient())

    // we will now begin testing the view mannequin
  }
}

Now that we’ve got a setup that we will check, it is time to take one other take a look at our testing objectives for the view mannequin. These testing objectives are what is going on to drive our choices for what we’ll put in our MockNetworkClient.

Writing our assessments

These are the assessments that I needed to write down for my submit fetching logic:

  • Ensure that I can fetch posts
  • Ensure that posts get up to date if the community returns new posts
  • Ensure that errors are dealt with accurately

Let’s begin including them one-by-one.

With the intention to check whether or not I can fetch posts, my mock community ought to most likely return some posts:

class MockNetworkClient: Networking {
  func fetchPosts() async throws -> [Post] {
    return [
      Post(id: UUID(), contents: "This is the first post"),
      Post(id: UUID(), contents: "This is post number two"),
      Post(id: UUID(), contents: "This is post number three")
    ]
  }

  // ...
}

With this in place, we will check our view mannequin to see if calling fetchPosts will truly use this listing of posts and replace the feed state accurately.

@Check func testFetchPosts() async throws {
  let viewModel = FeedViewModel(community: MockNetworkClient())

  await viewModel.fetchPosts()

  guard case .loaded(let posts) = viewModel.feedState else {
    Concern.report("Feed state shouldn't be set to .loaded")
    return
  }

  #count on(posts.rely == 3)
}

The second check would have us name fetchPosts twice to be sure that we replace the listing of posts within the view mannequin.

To ensure that us to manage our assessments totally, we must always most likely have a approach to inform the mock community what listing of posts it ought to return once we name fetchPost. Let’s add a property to the mock that permits us to specify a listing of posts to return from inside our assessments:

class MockNetworkClient: Networking {
  var postsToReturn: [Post] = []

  func fetchPosts() async throws -> [Post] {
    return postsToReturn
  }

  func createPost(withContents contents: String) async throws -> Submit {
    return Submit(id: UUID(), contents: contents)
  }
}

And now we will write our second check as follows:

@Check func fetchPostsShouldUpdateWithNewResponses() async throws {
  let consumer = MockNetworkClient()
  consumer.postsToReturn = [
    Post(id: UUID(), contents: "This is the first post"),
    Post(id: UUID(), contents: "This is post number two"),
    Post(id: UUID(), contents: "This is post number three")
  ]

  let viewModel = FeedViewModel(community: consumer)
  await viewModel.fetchPosts()

  guard case .loaded(let posts) = viewModel.feedState else {
    Concern.report("Feed state shouldn't be set to .loaded")
    return
  }

  #count on(posts.rely == 3)

  consumer.postsToReturn = [
    Post(id: UUID(), contents: "This is a new post")
  ]

  await viewModel.fetchPosts()

  guard case .loaded(let posts) = viewModel.feedState else {
    Concern.report("Feed state shouldn't be set to .loaded")
    return
  }

  #count on(posts.rely == 1)
}

The check is now extra verbose however we’re in full management over the responses that our mock community will present.

Our third check for fetching posts is to be sure that errors are dealt with accurately. Which means that we must always apply one other replace to our mock. The aim is to permit us to outline whether or not our name to fetchPosts ought to return a listing of posts or throw an error. We will use Outcome for this:

class MockNetworkClient: Networking {
  var fetchPostsResult: Outcome<[Post], Error> = .success([])

  func fetchPosts() async throws -> [Post] {
    return strive fetchPostsResult.get()
  }

  func createPost(withContents contents: String) async throws -> Submit {
    return Submit(id: UUID(), contents: contents)
  }
}

Now we will make our fetch posts calls succeed or fail as wanted within the assessments. Our assessments would now must be up to date in order that as a substitute of simply passing a listing of posts to return, we’ll present success with the listing. Here is what that might appear to be for our first check (I’m certain you’ll be able to replace the longer check based mostly on this instance).

@Check func testFetchPosts() async throws {
  let consumer = MockNetworkClient()
  consumer.fetchPostsResult = .success([
    Post(id: UUID(), contents: "This is the first post"),
    Post(id: UUID(), contents: "This is post number two"),
    Post(id: UUID(), contents: "This is post number three")
  ])

  let viewModel = FeedViewModel(community: consumer)

  await viewModel.fetchPosts()

  guard case .loaded(let posts) = viewModel.feedState else {
    Concern.report("Feed state shouldn't be set to .loaded")
    return
  }

  #count on(posts.rely == 3)
}

Information that we will present successful or failure for our assessments. We will truly go on forward and inform our assessments to throw a particular failure.

@Check func fetchPostsShouldUpdateWithErrors() async throws {
  let consumer = MockNetworkClient()
  let expectedError = NSError(area: "Check", code: 1, userInfo: nil)
  consumer.fetchPostsResult = .failure(expectedError)

  let viewModel = FeedViewModel(community: consumer)
  await viewModel.fetchPosts()

  guard case .error(let error) = viewModel.feedState else {
    Concern.report("Feed state shouldn't be set to .error")
    return
  }

  #count on(error as NSError == expectedError)
}

We now have three assessments that check our view mannequin.

What’s attention-grabbing about these assessments is that all of them rely on a mock community. Which means that we’re not counting on a community connection. However this additionally does not imply that our view mannequin and community consumer are going to work accurately.

We have not examined that our precise networking implementation goes to assemble the precise requests that we count on it to create. With the intention to do that we will leverage one thing known as URLProtocol.

Mocking responses with URLProtocol

Understanding that our view mannequin works accurately is basically good. Nevertheless, we additionally wish to be sure that the precise glue between our app and the server works accurately. That signifies that we must be testing our community consumer in addition to the view mannequin.

We all know that we should not be counting on the community in our unit assessments. So how will we get rid of the precise community from our networking consumer?

One method might be to create a protocol for URLSession and stuff every thing out that manner. It is an choice, but it surely’s not one which I like. I a lot desire to make use of one thing known as URLProtocol.

Once we use URLProtocol to mock out our community, we will inform URLSession that we must be utilizing our URLProtocol when it is making an attempt to make a community request.

This enables us to take full management of the response that we’re returning and it signifies that we will be sure that our code works without having the community. Let’s check out an instance of this.

Earlier than we implement every thing that we’d like for our check, let’s check out what it appears wish to outline an object that inherits from URLProtocol. I am implementing a few fundamental strategies that I’ll want, however there are different strategies accessible on an object that inherits from URLProtocol.

I extremely advocate you check out Apple’s documentation in the event you’re thinking about studying about that.

Organising ur URLProtocol subclass

For the assessments that we have an interest implementing, that is the skeleton class that I will be working from:

class NetworkClientURLProtocol: URLProtocol {
  override class func canInit(with request: URLRequest) -> Bool {
    return true
  }

  override class func canonicalRequest(for request: URLRequest) -> URLRequest {
    return request
  }

  override func startLoading() {
    // we will carry out our faux request right here
  }
}

Within the startLoading operate, we’re speculated to execute our faux community name and inform the consumer (which is a property that we inherit from URLProtocol) that we completed loading our knowledge.

So the very first thing that we have to do is implement a manner for a consumer of our faux community to supply a response for a given URL. Once more, there are a lot of methods to go about this. I am simply going to make use of probably the most fundamental model that I can provide you with to be sure that we do not get slowed down by particulars that can fluctuate from challenge to challenge.

struct MockResponse {
  let statusCode: Int
  let physique: Information
}

class NetworkClientURLProtocol: URLProtocol {
  // ...

  static var responses: [URL: MockResponse] = [:]
  static var validators: [URL: (URLRequest) -> Bool] = [:]
  static let queue = DispatchQueue(label: "NetworkClientURLProtocol")

  static func register(
    response: MockResponse, requestValidator: @escaping (URLRequest) -> Bool, for url: URL
  ) {
    queue.sync {
      responses[url] = response
      validators[url] = requestValidator
    }
  }

  // ...
}

By including this code to my NetworkClientURLProtocol, I can register responses and a closure to validate URLRequest. This enables me to check whether or not a given URL leads to the anticipated URLRequest being constructed by the networking layer. That is notably helpful whenever you’re testing POST requests.

Notice that we have to make our responses and validators objects static. That is as a result of we will not entry the precise occasion of our URL protocol that we’ll use earlier than the request is made. So we have to register them statically after which afterward in our begin loading operate we’ll pull out the related response invalidator. We have to be sure that we synchronize this by means of a queue so we’ve got a number of assessments working in parallel. We’d run into points with overlap.

Earlier than we implement the check, let’s full our implementation of startLoading:

class NetworkClientURLProtocol: URLProtocol {
  // ...

  override func startLoading() {
    // make sure that we're good to...
    guard let consumer = self.consumer,
      let requestURL = self.request.url,
      let validator = validators[requestURL],
      let response = responses[requestURL]
    else { 
      Concern.report("Tried to carry out a URL Request that does not have a validator and/or response")
      return 
    }

        // validate that the request is as anticipated
    #count on(validator(self.request))

    // assemble our response object
    guard let httpResponse = HTTPURLResponse(
      url: requestURL, 
      statusCode: response.statusCode, httpVersion: nil,
      headerFields: nil
    ) else {
      Concern.report("Not in a position to create an HTTPURLResponse")
      return 
    }

    // obtain response from the faux community
    consumer.urlProtocol(self, didReceive: httpResponse, cacheStoragePolicy: .notAllowed)
    // inform the URLSession that we have "loaded" knowledge
    consumer.urlProtocol(self, didLoad: response.physique)
    // full the request
    consumer.urlProtocolDidFinishLoading(self)
  }
}

The code accommodates feedback on what we’re doing. When you may not have seen this sort of code earlier than, it must be comparatively self-explanatory.

Implementing a check that makes use of our URLProtocol subclass

Now that we’ve received startLoading carried out, let’s try to use this NetworkClientURLProtocol in a check…

class FetchPostsProtocol: NetworkClientURLProtocol { }

struct NetworkClientTests {
  func makeClient(with protocolClass: NetworkClientURLProtocol.Kind) -> NetworkClient {
    let configuration = URLSessionConfiguration.default
    configuration.protocolClasses = [protocolClass]
    let session = URLSession(configuration: configuration)
    return NetworkClient(urlSession: session)
  }

  @Check func testFetchPosts() async throws {
    let networkClient = makeClient(with: FetchPostsProtocol.self)

    let returnData = strive JSONEncoder().encode([
      Post(id: UUID(), contents: "This is the first post"),
      Post(id: UUID(), contents: "This is post number two"),
      Post(id: UUID(), contents: "This is post number three"),
    ])

    let fetchPostsURL = URL(string: "https://practicalios.dev/posts")!

    FetchPostsProtocol.register(
      response: MockResponse(statusCode: 200, physique: returnData),
      requestValidator: { request in
        return request.url == fetchPostsURL
      },
      for: fetchPostsURL
    )

    let posts = strive await networkClient.fetchPosts()
    #count on(posts.rely > 0)
  }
}

The very first thing I am doing on this code is creating a brand new subclass of my NetworkClientProtocol. The explanation I am doing that’s as a result of I might need a number of assessments working on the similar time.

For that cause, I would like every of my Swift check capabilities to get its personal class. This may be me being somewhat bit paranoid about issues overlapping by way of when they’re known as, however I discover that this creates a pleasant separation between each check that you’ve got and the precise URLProtocol implementation that you just’re utilizing to carry out your assertions.

The aim of this check is to be sure that once I ask my community consumer to go fetch posts, it truly performs a request to the proper URL. And given a profitable response that accommodates knowledge in a format that’s anticipated from the server’s response, we’re in a position to decode the response knowledge into a listing of posts.

We’re primarily changing the server on this instance, which permits us to take full management over verifying that we’re making the proper request and still have full management over regardless of the server would return for that request.

Testing a POST request with URLProtocol

Now let’s see how we will write a check that makes certain that we’re sending the proper request once we’re making an attempt to create a submit.

struct NetworkClientTests {
  // ...

  @Check func testCreatePost() async throws {
    let networkClient = makeClient(with: CreatePostProtocol.self)

    // arrange anticipated knowledge
    let content material = "It is a new submit"
    let expectedPost = Submit(id: UUID(), contents: content material)
    let returnData = strive JSONEncoder().encode(expectedPost)
    let createPostURL = URL(string: "https://practicalios.dev/create-post")!

    // register handlers
    CreatePostProtocol.register(
      response: MockResponse(statusCode: 200, physique: returnData),
      requestValidator: { request in
        // validate fundamental setup
        guard 
          let httpBody = request.streamedBody,
          request.url == createPostURL,
          request.httpMethod == "POST" else {
            Concern.report("Request shouldn't be a POST request or would not have a physique")
            return false
        }

        // guarantee physique is right
        do {
          let decoder = JSONDecoder()
          let physique = strive decoder.decode([String: String].self, from: httpBody)
          return physique == ["contents": content]
        } catch {
          Concern.report("Request physique shouldn't be a sound JSON object")
          return false
        }
      },
      for: createPostURL
    )

    // carry out community name and validate response
    let submit = strive await networkClient.createPost(withContents: content material)
    #count on(submit == expectedPost)
  }
}

There’s numerous code right here, however general it follows a fairly comparable step to earlier than. There’s one factor that I wish to name your consideration to, and that’s the line the place I extract the HTTP physique from my request inside the validator. As a substitute of accessing httpBody, I am accessing streamedBody. This isn’t a property that usually exists on URLRequest, so let’s discuss why I would like that for a second.

Once you create a URLRequest and execute that with URLSession, the httpBody that you just assign is transformed to a streaming physique.

So whenever you entry httpBody inside the validator closure that I’ve, it should be nil.

As a substitute of accessing that, we have to entry the streaming physique, collect the info, and return alll knowledge.

Here is the implementation of the streamedBody property that I added in an extension to URLRequest:

extension URLRequest {
  var streamedBody: Information? {
    guard let bodyStream = httpBodyStream else { return nil }
    let bufferSize = 1024
    let buffer = UnsafeMutablePointer<UInt8>.allocate(capability: bufferSize)
    var knowledge = Information()
    bodyStream.open()
    whereas bodyStream.hasBytesAvailable {
      let bytesRead = bodyStream.learn(buffer, maxLength: bufferSize)
      knowledge.append(buffer, rely: bytesRead)
    }
    bodyStream.shut()
    return knowledge
  }
}

With all this in place, I will now verify that my community consumer constructs a totally right community request that’s being despatched to the server and that if the server responds with a submit like I count on, I am truly in a position to deal with that.

So at this level, I’ve assessments for my view mannequin (the place I mock out the complete networking layer to be sure that the view mannequin works accurately) and I’ve assessments for my networking consumer to be sure that it performs the proper requests on the right occasions.

In Abstract

Testing code that has dependencies is at all times somewhat bit difficult. When you’ve got a dependency you will wish to mock it out, stub it out, take away it or in any other case cover it from the code that you just’re testing. That manner you’ll be able to purely check whether or not the code that you just’re thinking about testing acts as anticipated.

On this submit we checked out a view mannequin and networking object the place the view mannequin relies on the community. We mocked out the networking object to be sure that we might check our view mannequin in isolation.

After that we additionally needed to write down some assessments for the networking object itself. To try this, we used a URLProtocol object. That manner we might take away the dependency on the server solely and totally run our assessments in isolation. We will now check that our networking consumer makes the proper requests and handles responses accurately as effectively.

Which means that we now have end-to-end testing for a view mannequin and networking consumer in place.

I don’t typically leverage URLProtocol in my unit assessments; it’s primarily in complicated POST requests or flows that I’m thinking about testing my networking layer this deeply. For easy requests I are likely to run my app with Proxyman connected and I’ll confirm that my requests are right manually.

author avatar
roosho Senior Engineer (Technical Services)
I am Rakib Raihan RooSho, Jack of all IT Trades. You got it right. Good for nothing. I try a lot of things and fail more than that. That's how I learn. Whenever I succeed, I note that in my cookbook. Eventually, that became my blog. 
rooshohttps://www.roosho.com
I am Rakib Raihan RooSho, Jack of all IT Trades. You got it right. Good for nothing. I try a lot of things and fail more than that. That's how I learn. Whenever I succeed, I note that in my cookbook. Eventually, that became my blog. 

Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here


Latest Articles

author avatar
roosho Senior Engineer (Technical Services)
I am Rakib Raihan RooSho, Jack of all IT Trades. You got it right. Good for nothing. I try a lot of things and fail more than that. That's how I learn. Whenever I succeed, I note that in my cookbook. Eventually, that became my blog.