Faking network responses in tests

Modern applications often rely on data from a network connection to work as intended. Sometimes they rely heavily on the network and are almost worthless without an internet connection while other apps can function mostly fine without a network connection. What these apps have in common is that they contain code that might be challenging to write tests for.

Whenever you write unit tests, you should always strive to make your tests as predictable, reproducible and most importantly independent of external factors as possible. This is a huge difference compared to integration testing where you’d test a certain part of a system within its context. An internet connection is quite possibly one of the most unpredictable factors that you do not want to introduce in a unit test suite.

Why is the network so unpredictable you ask? Reasons include but are not limited to:

  • The network connection might suddenly drop, this is outside of your control.
  • You might not control the server, changes, bugs or outages on the server would impact your tests.
  • Network speeds might vary depending on several factors, this could result in unjust test failures.

There are several things you can do to remove the network as a dependency from your tests. In this post, I will show you how you can build a dedicated networking layer that is abstracted using protocols. This allows you to swap out the networking implementation when running tests, which helps you avoid going to the network altogether. A setup like this allows you to test logic completely independent from what might be happening on a server. Let’s say, for example, that you’re building a reward system. Whenever a user is eligible for a reward, they tap a button. Your app fires off a request to a web server that returns a response to your app that contains between 0 and 2 rewards. There are four reward types:

  1. Jackpot, you win a free ticket for an event.
  2. Normal reward, you win a 50% ticket discount for an event.
  3. Small reward, you win a 25% ticket discount for an event.
  4. Loyalty points, you win between 50 and 100 loyalty points.

If a user wins the jackpot they should not receive any other prices. A user cannot receive the Small and Normal reward at once. A user can, however, get the Normal or Small reward together with loyalty points.

If a user wins the Jackpot, they should be shown a special Jackpot animation. If a user wins the Normal or Small award, they should be taken to a shopping page where they can use their discount if they desire to do so. If they win loyalty points and a reward, they should see a small banner at the top of the screen and they should also be taken to the shopping page. If a user only wins loyalty points, they should only be shown a banner that informs them about their loyalty points.

These are a lot of different paths that might have to be taken in the app, and testing this with a network connection on a real server isn’t feasible. The selection process is random after all! Complex flows like the one described above are another reason that makes faking network responses so convenient. Let’s get started with implementing this complex feature!

Designing the basics

Since this is a complex feature, we’re going to pick and choose a couple of elements from different common app architectures to use as building blocks. As described in the introduction, we’re building a feature where a user is on a certain screen and depending on the result of a network call, they will be taken to a new screen, and potentially they will see an overlay banner. This sounds like a great job for a ViewModel, and we’ll want to extract the networking logic into an object that the ViewModel can use to make network calls.

In this blog post, we will focus on tests for the networking layer.

Preparing the fake responses

When you’re dealing with responses from a server, you often have some kind of documentation or example response to work from. In this case, we have an example JSON response that contains all the information needed to derive every response we can expect from the server:

{
  "rewards": [
    {
      "type": "jackpot" // jackpot, regular or small
    }
  ],
    "loyalty_points": 60 // value between 50 and 100 or null
}

This relatively simple document contains all the information we need right now. It’s tempting to create a bunch of JSON files with different compositions of valid responses and use those while testing. Not a bad approach but we can do better. Since we have the Codable protocol in Swift, we can easily convert JSON data into model objects, but this also works the other way around! We can define our model in the main project, make it Codable and then spawn instances of the model in our test suite, convert them to JSON data and use those as inputs for the networking client. Here’s the model definition we’re using:

struct RewardsResponse: Codable {
  let rewards: [Reward]
  let loyaltyPoints: Int?
}

struct Reward: Codable {
  enum RewardType: String, Codable {
    case jackpot, regular, small
  }

  let type: RewardType
}

This simple model should be sufficient to hold the example JSON data. Let’s add a new file to the test target too, it should be a new Swift file named RewardsResponseFactory. This factory is going to help you create RewardsResponse objects and it will contain a convenient extension on RewardsResponse so you can easily convert the response object to JSON using a JSONEncoder. Add the following code to RewardsResponseFactory.swift:

@testable import RewardsApp

class RewardsResponseFactory {
  static func createResponseWithRewards(_ types: [Reward.RewardType], points: Int?) -> RewardsResponse {
    let rewards = types.map { type in Reward(type: type) }
    return RewardsResponse(rewards: rewards, loyaltyPoints: points)
  }
}

extension RewardsResponse {
  var dataValue: Data {
    let encoder = JSONEncoder()
    encoder.keyEncodingStrategy = .convertToSnakeCase
    return try! encoder.encode(self)
  }
}

You know have everything in place to start writing some tests and implementing the complex reward flow!

Writing your tests and implementation code

Like good practitioners of TDD, we’ll begin implementing our feature in tests first. Once we have a couple of tests outlined we can go ahead and begin implementing the code for our feature.

In Xcode’s Project Navigator, find the test target and rename the default test file and class to RewardsServiceTests. Remove the default code from Xcode’s test template. You should now have the following contents in RewardsServiceTests:

import XCTest
@testable import RewardsApp

class RewardsServiceTests: XCTestCase {
}

We’ll begin by adding tests for the API itself, we want to make sure that the API makes the correct requests and that it converts the JSON data that it receives to RewardResponse objects which are then passed back to the caller of the RewardsService’s fetchRewards method. Quite the mouthful but what we’re testing here is whether the RewardsService can make and handle requests. Since we can’t use the internet in our test, we’ll need to abstract the networking behind a protocol called Networking, add a new file called Networking.swift to your application target and add the following contents to it:

protocol Networking {
  func fetch(_ url: URL, completion: @escaping (Data?, URLResponse?, Error?) -> Void)
}

This protocol will allow us to use a fake networking object in our tests and a URLSession in the app. You can use the following extension on URLSession to make it usable as a Networking object in your application:

extension URLSession: Networking {
  func fetch(_ url: URL, completion: @escaping (Data?, URLResponse?, Error?) -> Void) {
    self.dataTask(with: url) { data, response, error in
      completion(data, response, error)
    }.resume()
  }
}

To write your test, create a new file in your test target and name it MockNetworking.swift. This file will hold the code used to fake networking responses. Don’t forget to add an @testable import statement for the app target and add the following implementation to this file:

class MockNetworking: Networking {
  var responseData: Data?
  var error: Error?

  func fetch(_ url: URL, completion: @escaping (Data?, URLResponse?, Error?) -> Void) {
    completion(responseData, nil , error)
  }
}

Simple as it might look, this mock networking object is really powerful. You can assign the response data and error, and the networking object will simply invoke the fetch completion handler callback with your predefined response. Users of the mock networking object won’t notice the difference between going to the network or receiving this predefined response which is exactly what we want because now you can use the mock network instead of a URLSession without any changes to your RewardsService. Let’s write some tests for the rewards service and then implement the service itself. Add the following code to the RewardsServiceTests file.

class RewardsServiceTests: XCTestCase {
  func testRewardServiceCanReceiveEmptyRewardsWithNoPoints() {
    let expect = expectation(description: "Expected test to complete")

    let response = RewardsResponseFactory.createResponseWithRewards([], points: nil)
    let mockNetwork = MockNetworking()
    mockNetwork.responseData = response.dataValue
    let service = RewardsService(network: mockNetwork)
    service.fetchRewards { result in
      guard case .success(let response) = result else {
        XCTFail("Expected the rewards to be fetched")
        return
      }

      XCTAssertTrue(response.rewards.isEmpty)
      XCTAssertNil(response.loyaltyPoints)
      expect.fulfill()
    }

    waitForExpectations(timeout: 1, handler: nil)
  }
}

This code uses the factory you created earlier to generate a response object. The extension on RewardsResponse that you added to the test target is used to convert the response into JSON data and you assign this data to the mock network. Next, the rewards service is initialized with the mock network and we call its fetchRewards method with a completion closure that fulfills the test expectation and checks whether the rewards response has the expected values.

Tip:
If you’re not quite sure what a test expectation is, check out Getting Started With Unit Testing - part 2. This blog post covers the basics of testing asynchronous code, including test expectations.

Your tests don’t compile yet because you haven’t implemented the RewardsService yet. Add a new Swift file called RewardsService.swift to your application target and add the following implementation to this file:

struct RewardsService {
  let network: Networking

  func fetchRewards(_ completion: @escaping (Result<RewardsResponse, Error>) -> Void) {
    network.fetch(URL(string: "https://reward-app.com/rewards")!) { data, urlResponse, error in
      let decoder = JSONDecoder()

      decoder.keyDecodingStrategy = .convertFromSnakeCase

      guard let data = data,
        let response = try? decoder.decode(RewardsResponse.self, from: data) else {
          // You should call the completion handler with a .failure result and an error
          return
      }

      completion(.success(response))
    }
  }
}

This is a pretty minimal implementation and that’s fine. Since we’re separating concerns in this little feature as much as possible it isn’t surprising that every single bit of implementation code we’ve written so far was fairly small; every bit of out code only does a single thing, making it easy to use in a test. Go ahead and run your test now, it should succeed!

Try adding a couple more test cases for using the following calls to the RewardsResponseFactory:

let response = RewardsResponseFactory.createResponseWithRewards([.jackpot], points: nil)
let response = RewardsResponseFactory.createResponseWithRewards([.regular], points: 50)
let response = RewardsResponseFactory.createResponseWithRewards([], points: 100)
let response = RewardsResponseFactory.createResponseWithRewards([.small, .regular], points: 70)
let response = RewardsResponseFactory.createResponseWithRewards([.small, .regular], points: nil)

There are many more possible combinations for you to test, go ahead and add as many as you want. Don’t forget to keep your code DRY and use XCTest's setUp method for shared configuration to avoid lots of boilerplate code in every test.

Next steps

If you have implemented the tests I suggested, you should currently have a pretty solid test suite set up for the RewardsService. Your suite tests whether the service can decode many different shapes of responses. If you got stuck implementing these tests, fear not. You can go to this blog post’s accompanying Github Repository and look at the ServiceTests folder to see the results.

But the feature I described at the start of this post isn’t complete yet! Try to add some tests for a RewardsViewModel, maybe add some tests to see whether your coordinator can determine the correct next view controller based on the response from the RewardsService. In the Completed folder in this post’s Github Repository, you’ll find an example of a completed test suite that covers the entire rewards feature!

In summary

In this post, you have learned a lot about testing code that involves using data that comes from a remote source. You saw how you can hide networking logic behind a protocol, allowing you to mock the object that’s responsible for ultimately making the network call.

I have also shown you how you can leverage extensions and Swift’s Codable protocol to generate response data based on the objects you ultimately want to decode. Of course, there are many other ways to obtain such mock data, for example by adding prepared JSON files to your test target, or possibly running a local web server that your tests can use instead of a remote server. These are all valid ways to test networking logic, but I use and prefer the method described in this post myself.

Thank you for reading this post! And as always, feedback, compliments, and questions are welcome. You can find me on Twitter if you want to reach out to me.

An in-depth look at for loops in Swift

In Swift, there is more than one way to loop over a list of elements. If we only consider the loops that aren't intended to create new lists or have side effects, you end up with two types of loops that are very similar; forEach and for item in list, or the for loop. In this week's Quick Tip, I will explain the difference between for and forEach loops, and you I will show some examples that should help you make a decision picking one of either loop methods, depending on your needs.

Understanding for loops

Commonly when you write code that need to do something with every item in a list (Array, Set or other Sequence), you might write something like the following:

let list = [1, 2, 3, 4, 5]

for item in list {
  // do something with item
}

This is possibly the most basic example of a for loop and I'm sure you have written loops like this at least once before you found this Quick Tip. So, what else can we do with for loops?

A cool feature that for loops have is that you can apply a filter to them using a where clause. This means that your loop body is only called for elements that meet the requirements specified in the where clause. The following code is an example of such a filter:

let list = [1, 2, 3, 4, 5]

for item in list where item > 2 {
  print(item)
}

The above code applies a where clause to the for loop that makes sure any element that we receive in the for loop's body is larger than two. This can make your for loop more readable than checking this criterion inside of the for loop body. This segues nicely into learning about the continue keyword that can be used in a for loop!

When you write continue in a for loop, you are telling Swift to stop executing the for loop body for the current item and move over to the next item in the list. The following code produces the same output as the for loop that has a where clause:

let list = [1, 2, 3, 4, 5]

for item in list {
  guard item > 2
    else { continue }

  print(item)
}

In terms of performance, you won't really notice a difference between using a guard or a where clause. You might think that the where clause would make sure you don't loop over as many items but this is not entirely true. Swift will still need to make sure whether each item meets the criteria specified in the where clause. The bigger difference in my personal opinion is readability and semantics. The version that has a where clause tells me exactly under what condition a certain item is passed on to the for loop's body, but it also makes it very easy to spot that there is a condition present in the first place. In the example with the guard, you need to spend a little bit more time looking at the code to figure out that we need the item to be larger than two. Of course, a where clause isn't always the best option, especially if there are multiple, potentially complicated, reasons for an item to be considered skippable.

Sometimes, you don't want to loop over every item in a list. You might want to only loop until you have found a certain item, much like Swift's first(where:) method does. Or maybe you just want to collect items from an array of numbers until you've reached a certain accumulated value. An example of this is shown in the following code snippet:

let list = [1, 2, 3, 4, 5]
var accumulated = 0

for item in list {
  accumulated += item

  if accumulated >= 4 {
    break
  }
}
print(accumulated)

This code loops through items and adds them to an accumulated value. Once this accumulated value is larger than or equal to 4, the break keyword is used to break out of the for loop. Doing this abandons the loop altogether, unlike continue which will only cancel the current executing of the for loop body, allowing the next item to be processed. In the example above, we would loop over 1, then 2 and finally 3. This puts the accumulated value at 6, and we will never reach the 4 or 5 in the list since the loop is cancelled using the break keyword.

Another way to break out of a for loop early is by returning a value:

let list = [1, 2, 3, 4, 5]

func findFirstItemLargerThan(_ threshold: Int) -> Int? {
  for item in list {
    if item > threshold {
      return item
    }
  }

  return nil
}

print(findFirstItemLargerThan(3))

Returning a value from a for loop will prevent further invocations of the loop body and immediately returns the provided value to the caller of the method that contains the for loop. This can be useful if you're implementing a function that should find the first match in a list, like in the example above.

Two other important features of for loops are the ability to use named for loops, and the ability to nest for loops. Let's look at both of these features in a single example:

let list = [1, 2, 3, 4, 5]

outerLoop: for item in list {
  for nestedItem in list {
    if item == nestedItem {
      continue outerLoop
    }

    print(item, nestedItem)
  }
}

By prepending the outer for loop with the outerLoop: label, we can now refer to it from inside of the inner loop. In the preceding code example, we loop over a list of integers in the outer loop. We loop over the same list in the inner loop. If we would omit the if statement and continue statement you would see an output that roughly looks like this:

1 1
1 2
1 3
1 4
1 5
2 1

//etc...

With the continue outerLoop statement in place, you see the following output:

2 1
3 1
3 2
4 1

//etc...

If you try removing the outerLoop label from the continue statement, you'll find that only the entries where item and nestedItem are equal will not be printed since the inner loop would move on to the next item in the inner list. By putting the outerLoop after the continue statement, the outer loop itself will proceed to the next item in the outer list. Pretty powerful stuff!

In this short introduction to for loops, you have seen several features and ways to use for loops. However, I promised you a comparison between for and forEach, so let's have a look at what a forEach loop is, and how it's different from a for loop.

Comparing for loops to forEach loops

Honestly, it's almost unfair to compare for loops to forEach loops, because if you look at how forEach is defined in the Swift language you might quickly understand how they're different:

@inlinable
public func forEach(_ body: (Element) throws -> Void) rethrows {
  for element in self {
    try body(element)
  }
}

We can see that forEach is a method that takes a closure and uses a for loop to iterate over all of the elements in the list and invokes the closure with each element as the argument. Let's look at a simple usage example:

let list = [1, 2, 3, 4, 5]

list.forEach { item in
  print(item)
}

So, what are the differences between for and forEach then? If they both are really just a for loop, they must be pretty much the same right? Unfortunately, they are not the same. A forEach loop lacks the capability to apply a where clause, use a continue statement, you can't return anything from a forEach loop and you also can't break out of it. You can nest forEach loops but you can't label them and you can't access (or continue / break / return from) the outer loop.

A forEach will always iterate over all elements in your list and apply whatever closure you pass it to every single element in your list, no exceptions.

In summary

In this blog post, you learned a lot about for loops, and you saw how Swift implements the forEach method. Now that you understand the differences between the two different ways of looping, you should be able to determine which loop you need. Do you need to break out of the loop early? Or maybe you want to use a where clause to prevent the loop body from being called for every item. Or maybe you're just looking for the first item to match a certain condition and you want to return that item. In all of these scenarios you'll want to use a for loop or a built-in function like first(where:) from the Swift standard library.

Alternatively, if you want to loop over every single item in an array or another type of list, without any exceptions, you can safely use forEach since that's what it's good at.

Thanks for reading this blog post and as always, your input, feedback, and questions are highly appreciated. Reach out on Twitter if you want to get in touch!

Optimizing Your Application’s Reviews

We all love the idea of getting loads of App Reviews, preferably with five stars and a description that explains why our apps are amazing. Unfortunately, users often don’t take the time to reviews the apps they enjoy. Instead, users will review your app when they’re unhappy. If something about your app doesn’t please them or something doesn’t work as expected, they will happily write an angry review and rate your app one star. So how do we get people to review apps when they are happy so they give us five stars? That’s exactly what I would like to discuss in this blogpost. By the end of this post you will have learned the following:

  • How to use iOS’ built-in review prompt to allow users to rate your app without going to the App Store.
  • Timing your review prompt to avoid frustrating your users.

Let’s dive right in, shall we?

Using iOS’ built-in review prompt

In iOS 10.3 Apple shipped an amazing little feature that might have gone unnoticed for many people. It’s called SKStoreReviewController and Apple describes it as follows:

An object that controls the process of requesting App Store ratings and reviews from users.

Sounds good right? Before I explain how you can use it in your app, let’s see what it does:

In the above screenshot, you can see a neat prompt that asks a user to review the current app. They can immediately tap the number of stars they want to give your app and they can choose to write a review if they please. Because this prompt is so straightforward to use, users are far more likely to rate your app than when you would show them a different kind of alert. For example, the following alert is far less likely to get the same amount of engagement as the SKStoreReviewController version.

Using the SKStoreReviewController is astonishingly straightforward. The following code is all you need to show a rating prompt:

SKStoreReviewController.requestReview()

Don’t forget to import StoreKit in the file where you wish to ask for reviews.

When your app is in production and you call requestReview, iOS will not always present a review prompt to the user. For example, if they have already rated your app or choose not to rate your app, iOS might choose to not ask the user again for a while until you have shipped an app update.

Now that you know how to ask for a review, let’s see how you can optimize when to ask your users to review your app.

Improving your review prompt strategy

Some apps are very eager to ask users for reviews and they don’t have a good strategy in place. Or any strategy at all for that matter. When a user has just downloaded your app, it’s not the best time to ask them for reviews. They are exploring your app, trying to figure out what it does and whether it suits their needs. They are still forming an opinion to see if they wish to use your app more often, or maybe look further for a different app that has similar features. Asking for a review at that moment isn’t a great look, your app will come across as a little pushy, inconsiderate and not very optimized for the user. This will be review prompt strategy lesson one:

Lesson one:
Don’t ask users for review during their first time using the app. Wait until the user has used your app a couple of times over a several day period.

Often users will come to your app with a goal. Sometimes the goal is small, like checking the weather and sometimes the goal is more complex, like writing a document or email. Interrupting users to ask them to review your app is unlikely to make your users happy. They might decide to simply close your prompt and get on with their task. If they get interrupted by review prompts too often, they might just rate your app one star, just to get it over with. Instead, try to postpone asking for reviews until your user has achieved their goal. You are far more likely to get a positive response from your users.

Lesson two:
Interrupting users to get them to review your app isn’t great. Try asking for reviews at the end of a task, game or after the user has found the information they were looking for.

Lastly, if a user has just had a negative experience with your app, you might want to hold off asking for a review. Imagine you’ve built a game and the user has failed to complete a level several times. They’ve spent the better part of an hour trying but simply couldn’t succeed. Normally, you might consider a user that returns to the game’s home screen as a user that completed a task and ask them for a review. However, this particular user hasn’t had the best experience today so it’s best to ask them for a review some other time, maybe after they have completed a couple of levels in your game without. Users that just had a positive experience are far more likely to bless your app with a good review than users that are frustrated.

Lesson three:
Asking users for reviews when they just had a less than ideal experience isn’t going to give you the best results. Instead, try to ask for a review after the user has done something positive in your app, like win a game for example.

Knowing these lessons is great, but how can you determine the best time to ask for a review, especially if you don’t want to ask a user for a review on their first launch, or after they have just lost a game. In the next section, I will show you a simple approach that uses the UserDefaults storage to keep track of some statistics.

Implementing your review strategy

Keeping track of what a user has done in your app, and whether it’s a good time to ask for reviews is essential to get right. Luckily, it’s not very complex to store some basic information in UserDefaults. Personally, I like to wrap all logic related to collecting reviews in a ReviewManager object that has access to a UserDefaults store:

struct ReviewManager {
  let userDefaults: UserDefaults

  init(userDefaults: UserDefaults = UserDefaults.standard) {
    self.userDefaults = userDefaults
  }
}

Some of the basic functionality for this ReviewManager typically is to track the first launch date, the number of total launches and an askForReviewIfNeeded() method:

struct ReviewManager {
  let userDefaults: UserDefaults
  let minimumInstallTime: TimeInterval = 60 * 60 * 24 * 30 // 1 month
  let minimumLaunches = 10

  init(userDefaults: UserDefaults = UserDefaults.standard) {
    self.userDefaults = userDefaults
  }

  func trackLaunch() {
    if userDefaults.double(forKey: "firstLaunch") == 0.0 {
      userDefaults.set(Date().timeIntervalSince1970, forKey: "firstLaunch")
    }

    let numberOfLaunches = userDefaults.integer(forKey: "numberOfLaunches")
    userDefaults.set(numberOfLaunches + 1, forKey: "numerOfLaunches")
  }

  func askForReviewIfNeeded() {
    guard shouldAskForReview()
      else { return }

    SKStoreReviewController.requestReview()
  }

  private func shouldAskForReview() -> Bool {
    let timeInstalled = Date().timeIntervalSince1970 - userDefaults.double(forKey: "firstLaunch")
    let numberOfLaunches = userDefaults.integer(forKey: "numberOfLaunches")

    guard timeInstalled > minimumInstallTime,
      numberOfLaunches > minimumLaunches
      else { return false }

    return true
  }
}

Of course, you can expand the logic to determine whether it is appropriate to ask for a review, for instance by tracking the number of games played or won in a game, the number of documents created or other metrics that you think are relevant to your user. Make sure to pass your reviewManager around in your application to the relevant places so they can call askForReviewIfNeeded whenever you think it’s a good moment to do so.

Summary

In this post, you have learned about the SKStoreReviewController and how it allows you to prompt users to review your app in a convenient manner. You have also learned several essential rules to ensure you ask for reviews when users are most likely to leave you a positive review. Lastly, you also learned how you can keep track of a user’s behavior in your app by storing values in UserDefaults and using them to determine whether it’s a good time to ask your user for an app review.

As always, feedback, compliments, and questions are welcome. You can find me on Twitter if you want to reach out to me.

What is Module Stability in Swift and why should you care?

The Swift team has recently released Swift 5.1. This version of the Swift language contains many cool features like Function Builders that are used for SwiftUI and Property Wrappers that can be used to add extra functionality to properties. This release also contains a feature called Module Stability. But what is this feature? And what does it mean to you as a developer? In this week’s blog post, I will explain this to you. But before we get to Module Stability, let’s do a little time traveling back to Swift 5.0, which shipped with ABI (Application Binary Interface) Stability.

Understanding ABI Stability

Up to Swift 5.0, every time you built an app with Swift, your app would include the entire Swift standard library and runtime. The reason for this was that Swift was changing so much that it wasn’t feasible to include the Swift fundamentals into releases of iOS. Swift 5.0 changed this with ABI Stability. Swift now defines a stable binary interface that will not change across Swift versions. In practice, this means that the Swift 5.1 (or later) runtime can execute code that was compiled with the Swift 5.0 compiler due to this stable interface definition.

When you write code, you do not need to understand or think about this binary interface, or ABI. This is all handled internally by the Swift compiler and it’s possible that the biggest win for your app is simply that your application binary is smaller for iOS versions that include the Swift runtime because the App Store will now strip the Swift runtime out of your binary if your app is being downloaded on a platform that has ABI Stability.

One common misunderstanding I’ve seen with ABI stability is that people were hoping that it would allow them to use frameworks that are compiled with Swift 5.0 in a project that uses the Swift 5.1 compiler. Unfortunately, this is not the case and the following error is likely one that you’ve encountered when you upgraded from Swift 5.0.1 to Swift 5.1.

Module compiled with Swift 5.0.1 cannot be imported by the Swift 5.1 compiler

This brings us to the Module Stability introduced by Swift 5.1.

Understanding Module Stability

While Module Stability and ABI stability are completely different animals altogether, they are also quite similar. While ABI Stability allows programs written with different versions of Swift to exist in a shared runtime, Module Stability allows you to use frameworks compiled with different versions of swift in a project that might use yet another version of Swift. The way this works is best explained by the following excerpt from the Swift 5.1 release announcement:

Swift 5.1 enables the creation of binary frameworks that can be shared with others leveraging the language’s added support for module stability. Module stability defines a new text-based module interface file that describes the API of a binary framework, allowing it to be compiled with code using different versions of the compiler.

Binary frameworks that were built with Module Stability enabled contain a special folder with the .swiftmodule suffix which contains .swiftinterface and .swiftmodule files. These files are the text-based definition of your framework's API that is mentioned in the Swift 5.1 release announcements. To build your own frameworks with support for module stability you need to enable the Build Library for Distribution setting in your framework's Project Settings pane and set the Skip Install property to NO. After doing this, Xcode includes the .swiftmodule in your product when you archive it, making your framework "module stable".

Every binary framework that is compiled with the Swift 5.1 compiler and Xcode 11 or later can be used in any project that uses Swift 5.1 or later as long as the Build Library for Distribution and Skip Install properties are configured correctly. So, in short, Module Stability means that you should never see an error message that reads “Module compiled with Swift 5.1 cannot be imported by the Swift 5.2 compiler” because all frameworks that were built with module stability are automatically compatible with the Swift version it was built for and newer. Pretty cool, right?

In conclusion

Now that we have Module Stability and ABI Stability in Swift, the language is likely to change at a slower rate than we’re used to. We should see less radical, source breaking changes and the language should slowly mature into a beautiful, fast and stable language that will be a great basis for your applications for years to come. However, don’t think that Swift will stop innovating and evolving. The Swift Evolution forum is still going strong and folks are still working very hard to make Swift a versatile, safe and clean language to work with.

As always, feedback, compliments, and questions are welcome. You can find me on Twitter if you want to reach out to me.

Finding the difference between two Arrays

Many applications work with data, often they are built to retrieve data and display this data to the user in a table view, collection view, list (if you're using SwiftUI) or a different kind of component. It's not uncommon for this data to change and when it does you might be interested in figuring out what elements were added to the data and which items were removed. This isn't always straightforward so up until now you might have written code something like the following:

func didFetchNewRecipes(_ newRecipes: [Recipe]) {
  recipes = newRecipes
  tableView.reloadData()
}

Simple and effective, much easier than figuring out the difference and only reloading the cells that have changed. On iOS 13, you can use new methods on any collection that conforms to BiDirectionalCollection to determine the changes between two collections as long as the items that are contained in the collection conform to Equatable. You can then use the information from this method to update a table view:

func didFetchNewRecipes(_ newRecipes: [Recipe]) {
  let changes = newRecipes.difference(from: recipes)

  let insertedIndexPaths = changes.insertions.compactMap { change -> IndexPath? in
    guard case let .insert(offset, _, _) = change
      else { return nil }

    return IndexPath(row: offset, section: 0)
  }

  let removedIndexPaths = changes.removals.compactMap { change -> IndexPath? in
    guard case let .remove(offset, _, _) = change
      else { return nil }

    return IndexPath(row: offset, section: 0)
  }

  self.recipes = newRecipes
  tableView.beginUpdates()
  tableView.insertRows(at: insertedIndexPaths, with: .automatic)
  tableView.deleteRows(at: removedIndexPaths, with: .automatic)
  tableView.endUpdates()
}

Tip:
If your elements are not Equatable, use the differences(from:by:) method to supply a closure where you can perform your own comparison to determine if two elements are equal.

Cool stuff right? What kind of applications do you see for this kind of method? Let me know on Twitter!

Getting started with unit testing your Swift code on iOS – part 2

In part 1 of this two-part blog post, you’ve learned how to write synchronous unit tests for a login view model. As a reminder, you saw how to implement tests for the following requirements:

  • When both login fields are empty, pressing the login button should trigger an error that informs the user that both fields are mandatory.
  • When one of the two fields is empty, pressing the login button should trigger an error that informs the user that the empty field is mandatory.
  • When the user’s email address does not contain an @ symbol, pressing the login button should trigger an error that informs the user that they must provide a valid email address.

You implemented these requirements using a LoginFormViewModel that has two properties for the username and password and a login method. And of course, you wrote tests for every requirement listed. If you want to start following this blog post with the final code of the previous part, check out the HelloUnitTest-PartOne folder in this blog post’s GitHub repository.

In this second part, you will learn how to refactor the current test suite to support asynchronous code. You will also implement a networking abstraction that will act as a fake server for login requests.

The objective of this blog post

Just like in the previous post, you will implement tests for a couple of requirements. This time, there are just two requirements:

  • When the user’s credentials are filled in, but the server doesn’t recognize the email address or the password is wrong, an error should be triggered that informs the user that their credentials were incorrect.
  • When the user’s credentials are filled in and they are valid, the login method should invoke the completion handler with a User object.

So, without further ado, let’s dive right in and begin refactoring and writing some more test code!

Getting to work

The first requirement we’re going to implement is the following:

  • When the user’s credentials are filled in, but the server doesn’t recognize the email address or the password is wrong, an error should be triggered that informs the user that their credentials were incorrect.

Note that this requirement speaks of a server. In this post, I will not focus on the nitty-gritty details and many possibilities of setting up a mock networking layer. Instead, I will show you a basic form of abstracting a network layer so you can do testing with it. First, let’s refactor the existing code from part 1 so it’s ready for the new asynchronous nature of the tests you’re going to write in this post.

In order to make the form view model asynchronous, we’re going to change the code first and update the existing tests after. I will assume that your code looks identical to the code that’s in the HelloUnitTests-PartOne folder of this blog post’s GitHub repository. The login method on the LoginFormViewModel should be changed so it supports asynchronous execution. This means that the method should no longer throw errors and instead invoke a completion handler with a result of type Result<User, Error>. You will define user as an empty struct since we’re not going to actually decode and create User objects later. The definition of User should look as follows:

struct User {

}

Completely empty. Simple right? You can either put it in its own file or throw it in the LoginFormViewModel.swift file since it’s just an empty struct. Next, rewrite the login method as follows:

func login(_ completion: @escaping (Result<User, LoginError>) -> Void) {
  guard let password = password, let username = username else {
    if self.username == nil && self.password == nil {
      completion(Result.failure(LoginError.formIsEmpty))
    } else if self.username == nil {
      completion(Result.failure(LoginError.usernameIsEmpty))
    } else {
      completion(Result.failure(LoginError.passwordIsEmpty))
    }
    return
  }

  guard username.contains("@") else {
    completion(Result.failure(LoginError.emailIsInvalid))
    return
  }
}

If at this point you’re thinking: “Hey! This isn’t right. TDD dictates that we change our tests first”, you would be absolutely right. However, I find that sometimes it’s simpler to have a bunch of passing tests, then refactor your logic, and refactor the tests to match afterwards. It’s really a preference of mine and I wanted to make sure you see this method of writing code and tests too.

Let’s refactor the tests so they compile and pass again. I will only show you how to refactor a single test case, all the other test cases should be refactored in a similar way. Again, if you get stuck, refer to this blogpost’s GitHub repository. The solutions for this part of the post are in the HelloUnitTests-PartTwo folder. Refactor the testEmptyLoginFormThrowsError method in your test file as follows:

func testEmptyLoginFormThrowsError() {
  XCTAssertNil(loginFormViewModel.username)
  XCTAssertNil(loginFormViewModel.password)

  // 1
  let testExpectation = expectation(description: "Expected login completion handler to be called")

  loginFormViewModel.login { result in
    guard case let .failure(error) = result, case error = LoginError.formIsEmpty else {
      XCTFail("Expected completion to be called with formIsEmpty")
      testExpectation.fulfill()
      return
    }

    // 3
    testExpectation.fulfill()
  }

  // 2
  waitForExpectations(timeout: 1, handler: nil)
}

This test contains a couple of statements that you haven’t seen before. I have added comments with numbers in the code so we can go over them one by one.

  1. This line of code creates a so-called test expectation. An expectation is an object that is used often when testing asynchronous code. Expectations start out in an unfulfilled state and remain like that until you call fulfill on them. If you never call fulfill, your test will eventually be considered failed.
  2. At the end of the test method, waitForExpectation is called. This method instructs the test that even though the end of the control flow has been reached, the test is not done executing yet. In this case, the test will wait for one second to see if all expectations are eventually fulfilled. If after one second there are one or more unfulfilled expectations in this test, the test is considered to be failed.
  3. Since the login method is now asynchronous, it received a callback closure that uses Swift’s Result type. Once the result has been unpacked and we find the error that we expect, the expectation that was created earlier is fulfilled. If the expected error is not found, the test is marked as failed and the expectation is also fulfilled because the completion handler was called. There is no need to wait a full second for this test to fail since the XCTFail invocation already marks the test as failed.

Now that login supports callbacks, we can write a new test that makes sure that the LoginFormViewModel handles server errors as expected. To do this, we’re going to introduce a dependency for LoginFormViewModel. Using a specialized object for the networking in an application allows you to create a mock or fake version of the object in your test, which provides a high level of control of what data the fake network object responds with.

This time, we’re going to jump from implementation code into tests and then back into implementation code. First, add the following protocol definition to LoginFormViewModel.swift:

protocol LoginService {
  func login(_ completion: @escaping (Result<User, LoginError>) -> Void)
}

This protocol is fairly straightforward. It dictates that any networking object acting as a login service must have a login method that takes a completion handler that takes a Result<User, LoginError> object. This method signature is identical to the one you’ve already seen on the LoginFormViewModel. Next, add a property to the LoginFormViewModel to hold the new login service:

let loginService: LoginService

Finally, call the login method on the login service at the end of LoginFormViewModel’s login method:

func login(_ completion: @escaping (Result<User, LoginError>) -> Void) {
  // existing code

  guard username.contains("@") else {
    completion(Result.failure(LoginError.emailIsInvalid))
    return
  }

  loginService.login(completion)
}

So far so good, now let’s make the tests compile again. Add a property for the login service to your test class and update the setUp method as shown below:

var loginService: MockLoginService!

override func setUp() {
  loginService = MockLoginService()
  loginFormViewModel = LoginFormViewModel(loginService: loginService)
}

Note that this code uses a type that you haven’t defined yet; MockLoginService. Let’s implement that type now. Select your test suite’s folder in Xcode’s File Navigator and create a new Swift file. When naming the file, double check that the file will be added to your test target and not to your application target. Name it MockLoginService and press enter. Next, add the following implementation code to this file:

import Foundation
@testable import HelloUnitTests

class MockLoginService: LoginService {
  var result: Result<User, LoginError>?

  func login(_ completion: @escaping (Result<User, LoginError>) -> Void) {
    if let result = result {
      completion(result)
    }
  }
}

The code for this file is pretty simple. The mock login service has an optional result property that we’re going to give a value later in the test. The login method that will be called from the login view model immediately calls the completion block with the result that is defined by the test. This setup is a cool way to fake very basic network responses because your application code can now use any object that conforms to LoginService. You create and inject a real networking object if you’re starting your app normally, and you create and inject a mock networking object when you’re running tests. Neat!

Note that the MockLoginService is a class and not a struct. Making this object a class ensures that any assignments to result are applied to all objects that have a reference to the MockLoginService. If you’d make this object a struct, every object that receives a MockLoginService will make a copy and assignments to result would not carry over to other places where it’s used.

Your tests should still run and pass at this point. We are now ready to finally add that test we set out to write in the first place:

func testServerRespondsWithUnkownCredentials() {
  loginFormViewModel.username = "[email protected]"
  loginFormViewModel.password = "password"
  XCTAssertNotNil(loginFormViewModel.username)
  XCTAssertNotNil(loginFormViewModel.password)

  loginService.result = .failure(LoginError.incorrectCredentials)

  let testExpectation = expectation(description: "Expected login completion handler to be called")

  loginFormViewModel.login { result in
    guard case let .failure(error) = result, case error = LoginError.incorrectCredentials else {
      XCTFail("Expected completion to be called with incorrectCredentials")
      testExpectation.fulfill()
      return
    }

    testExpectation.fulfill()
  }

  waitForExpectations(timeout: 1, handler: nil)
}

The above code looks very similar to the asynchronous test code you’ve already written. The major difference here is on line 7: loginService.result = .failure(LoginError.incorrectCredentials). This line of code assigns a failure response with a specific error to the mock login service’s result property. All other code is pretty much unchanged when you compare it to your previous tests. Now add the incorrectCredentials error case to your LoginError enum and run the tests. They should pass, which indicates that the LoginFormViewModel uses a networking object and that it forwards errors that it received from the networking object to the calling object.

You now have enough information to implement the final test case yourself:

  • When the user’s credentials are filled in and they are valid, the login method should invoke the completion handler with a User object.

When you’re done and want to check your solution, or if you’re stuck, don’t hesitate to check out the GitHub repository for this post.

Next steps

First of all, congratulations on making it this far! You’ve achieved a lot by working through all of the examples shown in this two-part post. Part one showed you how to write simple unit tests, and with the information from this second part, you can even write tests for asynchronous code. Moreover, you now even have an idea of how you can architect your application code to be easily testable by using protocols as an abstraction layer.

Of course, there is much more to learn about testing. For now, you should have plenty of information to stay busy, but if you’re eager to start learning more, I can recommend taking a look at some other blog posts I wrote about testing:

Alternatively, you can try to refactor the test suite a little bit by removing some of the code duplication you’ll currently find in there. After all, test code is code too, and we should aim to make it as DRY and high-quality as possible.

Getting started with unit testing your Swift code on iOS – part 1

Recently, I ran a poll on Twitter and discovered that a lot of people are not sure how to get started writing tests, or they struggle to get time approved to write tests for their code. In this blogpost, I will take you through some of the first steps you can take to start writing tests of your own and help you pave the way to a more stable codebase.

Why bother with tests at all?

You might be wondering why you should bother with code that tests your code. When you put it like that, the idea might indeed sound silly. To others, tests sound so horribly complex that writing them can hardly be easier and more stable than just writing code and testing your app by hand.

These are valid thoughts, but think of the last time you had to test something like a preferences form where you needed to make sure that every input you throw at it produces the expected output. Especially if the form contains something like email validation, testing the form can be time-consuming because there are so many valid and invalid inputs. After executing a couple of manual tests, you’re probably confident that everything works as expected and you stop testing it altogether until a user finds a bug in your logic.

What if I told you that an automated test can do the exact thing that takes you several minutes to check in less than a second. That’s pretty amazing, right? Once a test is written, it is a part of your test suite forever. This means that any time you run the test suite, you’re not only testing the feature you’re working on; you’re testing every feature you’ve ever written. All in the time it takes you to grab a cup of coffee.

The confidence that you can gain from having a test suite is liberating. And the fact that you simply know that nothing broke and everything works pretty much exactly as planned (we all forget test cases from time to time), ensures that every time you commit your code, you do so with confidence.

The objective of this blog post

In this two-part blog post, you’re going to build a very simple login form. You won’t actually build the form itself, because all logic for the form will be encapsulated in a ViewModel that you will write tests for. Don’t worry if you don’t know MVVM, never used it, or simply don’t like it. The point of this post isn’t to convince you that MVVM is the one architecture to rule them all (it isn’t) but simply to have a somewhat real-world scenario to write tests for.

By the end of this post, you should have some understanding of how you can structure your code and tests. You should also be able to adapt this to virtually any other code base that you come across. Before we get to writing the tests, let’s define a couple of basic acceptance criteria that we’re going to write tests for in this post.

  • When both login fields are empty, pressing the login button should trigger an error that informs the user that both fields are mandatory.
  • When one of the two fields is empty, pressing the login button should trigger an error that informs the user that the empty field is mandatory.
  • When the user’s email address does not contain an @ symbol, pressing the login button should trigger an error that informs the user that they must provide a valid email address.

The requirements above are somewhat incomplete, but they should reflect a sensible set of tests that you might want to write for a login form. If you want to follow along with all code written in this post and the next, now is the time to launch Xcode and create a new project. Make sure you check the “Include Unit Tests” checkbox when creating the project. Since SwiftUI is still brand new and not everybody is able to use it in their projects, we’ll use a Storyboard based template for now. If you accidentally chose SwiftUI, that’s okay too, we’re not building UI in this post.

Writing your first test

If you’ve created your project with the “Include Unit Tests” checkbox enabled, you’ll find a folder that had the Tests suffix in the Project Navigator. This folder belongs to your test suite and it’s where you should add all your unit test files. The default file contains a couple of methods that are important to understand. Also note that there is a special import declaration near the top of the file. It has the @testable prefix and imports your app target. By importing your app or framework with the @testable prefix, you have access to all public and internal code that’s in your app or framework.

The setUp and tearDown methods

If your test code contains shared setup logic, you can use the setUp method to perform work that must be performed for every test. In our tests, we’ll use the setUp method to create an instance of the LoginFormViewModel. Similar to the setUp method, the tearDown method can be used to release or reset certain resources that you may have created for one test, but don’t want sticking around for the next test.

The test methods

In addition to setUp and tearDown, the default template Xcode provides contains two methods that have the test prefix. These test methods are your actual tests. Any method in an XCTectCase subclass with the test prefix is considered a test by XCTest and will be executed as part of your test suite.

The testPerformanceExample shows a very basic setup for performance testing. This is useful if you want to make sure that a complex algorithm you’re working on doesn’t become much slower in future iterations of your work. We’ll ignore that method for now, since we won’t be doing any performance testing in this post. Go ahead and remove testPerformanceExample from the file.

Now that you have an idea of what a test file contains, let’s rename the testExample to testEmptyLoginFormThrowsError and learn how to implement your first test!

Getting to work

Since you already have the test file open, let’s write a test for the login form first. After that, we can start implementing the form. First, add a property for the login form to the test class:

var loginFormViewModel: LoginFormViewModel!

As mentioned before, the setUp method should be used to create a new instance of the login form view model for every test case. Add the following implementation for setUp to create the view model:

override func setUp() {
  loginFormViewModel = LoginFormViewModel()
}

Your code doesn’t compile at this point. This is expected since the LoginFormViewModel is not defined yet. Writing tests before you write an implementation is common practice in Test Driven Development. You’ll create an implementation for LoginFormViewModel right after implementing your first test.

Every test we write will now have access to a fresh instance of LoginFormViewModel. The first test we’re going to write makes sure that when both the username and the password on the login form are empty and that we receive a specific error whenever we attempt to login. XCTest uses assertions to ensure that certain values are exactly what they should be. To check if something is nil for example, you use the XCTAssertNil method. Add the following two lines of code to testEmptyLoginFormThrowsError to assert that both username and password are nil:

XCTAssertNil(loginFormViewModel.username)
XCTAssertNil(loginFormViewModel.password)

If either the username or password property is true, XCTest will mark that assertion and the test itself as failed. XCTAssertNil is a fairly simple assertion. Let’s look at a somewhat more complex assertion to check whether login in throws an appropriate error:

XCTAssertThrowsError(try loginFormViewModel.login()) { error in
  guard case let LoginError.formIsEmpty = error else {
    XCTFail("Expected the thrown error to be formIsEmpty")
  }
}

The code above asserts that loginFormViewModel.login() throws an error. The XCTAssertThrowsError method accepts a closure that can be used to inspect the error that was thrown. In this case, we want to make sure that the thrown error is the expected error. If we receive the wrong error, we fail the test by calling XCTFail with a failure message. If your test fails due to login throwing an unexpected error, Xcode will show the message you provided to XCTFail in the console to provide some guidance in figuring out exactly why your test failed.

Now that you have an entire test prepared, let’s write the code that needs to be tested! Select your app’s folder in the file navigator and create a new Swift file called LoginFormViewModel. Define a new LoginFormViewModel struct and give it a username and password property that are both defined as var and of type String?. Also, define a login method and mark it as throwing by adding throws after the method signature.

Since your test expects a specific error to be thrown, define a new enum outside of the LoginFormViewModel and name it LoginError. Make sure it conforms to Error and add a single case to the enum called formIsEmpty. After creating the enum, add the following line of code to your login() method:

throw LoginError.formIsEmpty

This might seem strange to you. After all, shouldn’t login() only throw this error if both the username and password are nil? And you’d be absolutely correct. However, since we’re writing the test suite using an approach that is loosely based on Test Driven Development, we should only be writing code to make tests pass. And since we’ve only written a single test, the current implementation is correct based on the test suite. By working like this, you can be sure that you’re always testing all the code paths you want to test. Later in this blog post, you’ll refactor the login() method to do nil checking and more.

At this point, you should have the following contents in your LoginFormViewModel.swift file:

import Foundation

enum LoginError: Error {
  case formIsEmpty
}

struct LoginFormViewModel {
  var username: String?
  var password: String?

  func login() throws {
    throw LoginError.formIsEmpty
  }
}

Go back to your test file and run your tests by pressing cmd + u. Your project and test suite should build, and after a couple of seconds, you should get a notification saying that your tests passed. Note the green checkmarks next to your test class and the testEmptyLoginFormThrowsError method.

Congratulations! You have written your first test. Now let’s switch into second gear and walk through the process of expanding the tests for your view model.

Completing the objective

You have currently implemented one out of three tests that are needed for us to consider the objective to be complete. Both of the remaining tests are very similar to the test you wrote earlier.

  • When one of the two fields is empty, pressing the login button should trigger an error that informs the user that the empty field is mandatory.
  • When the user’s email address does not contain an @ symbol, pressing the login button should trigger an error that informs the user that they must provide a valid email address.

Try to implement these on your own. If you get stuck you can either look back at the test you already wrote or have a look at the GitHub repository that contains the completed test suite and implementation for this blog post. The solution for requirements one through three are in the folder HelloUnitTests-PartOne.

Hint: one of the requirements is best tested with two separate test cases and you should use an assertion method that confirms something is not nil.

If you’ve managed to implement both tests without looking at the solution on GitHub, I tip my hat to you, well done! If you did have to sneak a peek, don’t worry. You completed the assignment and you have learned a couple of things about writing tests!

Concluding part 1

In this first part of Getting started with unit testing on iOS you have learned how to write some basic, synchronous tests. Try coming up with some more tests you can execute on the view model, or maybe attempt to create a registration view model that has a couple of more fields and validation rules.

In the next part of this blog post, you will apply some major refactors to the test suite you’ve built in this post to do some asynchronous testing. You’ll also learn how to create a testable abstraction for networking logic! Grab some coffee, take a breath and click right through to Getting started with unit testing on iOS - part 2.

Supporting Low Data Mode in your app

Together with iOS 13, Apple announced a new feature called Low Data Mode. This feature allows users to limit the amount of data that’s used by apps on their phone. The low data mode setting is available in the settings app. Whenever a user is on a slow network, a very busy network or on a network that might charge them for every megabyte they download, users might not want to spend their limited data budget on large fancy images or clever prefetching logic. With Low Data Mode, users can now inform your app that they are on such a network so you can accommodate their needs accordingly.

It’s up to app developers to support low data mode and handle the user’s preferences with care. In their 2019 WWDC talk Advanced In Networking Part One, Apple suggests to at least adopt low data mode for features that will have limited to no impact on the user experience. A good example of this might be to limit the amount of data prefetching and syncing your app does to prevent making requests of which the result will never be visible to the user.

One clever integration example of low data mode that Apple gives in their talk is graceful degradation of images. Any time your app wants to load an image, it should fall back to a smaller, lower-quality version if low data mode is enabled. In this post, I will show you how you can implement a feature like this, and what other ways there are for you to integrate low data mode in your apps.

A simple fallback integration

In its most basic form, you can configure low data mode support separately for each request your app makes. The following code shows how you can create a request that will honor a user’s low data mode preferences:

guard let url = URL(string: "https://someresource.com")
  else { return }

var request = URLRequest(url: url)
request.allowsConstrainedNetworkAccess = false

By setting allowsConstrainedNetworkAccess to false on the URLRequest, you’ve done all the work needed to support low data mode. When you attempt to execute this URLRequest while Low Data Mode is active, it will fail with a URLError that has its networkUnavailableReason property set to .constrained. So whenever your request fails with that error, you might want to request a resource that consumes less data if needed, like a lower quality image. The following code snippet shows how to do this using the request from the previous snippet:

URLSession.shared.dataTask(with: request) { data, response, error in
  if let error = error {
    if let networkError = error as? URLError, networkError.networkUnavailableReason == .constrained {
      // make a new request for a smaller image
    }

    // The request failed for some other reason
    return
  }

  if let data = data, let image = UIImage(data: data) {
    // image loaded succesfully
    return
  }

  // error: couldn't convert the data to an image
}

I have omitted the error handling and the URLRequest for the smaller image because these features might be specific for your app and implementing them shouldn’t be too daunting.

In case you’re wondering how you can do this using Apple’s new Combine framework, I’ll gladly show you. The following snippet is a complete example of a method that accepts a high quality and low-quality image URL, attempts to load to high-quality one but falls back on the low-quality version if low data mode is enabled:

func fetchImage(largeUrl: URL, smallUrl: URL) -> AnyPublisher<UIImage, Error> {
  var request = URLRequest(url: largeUrl)
  request.allowsConstrainedNetworkAccess = false

  return URLSession.shared.dataTaskPublisher(for: request)
    .tryCatch { error -> URLSession.DataTaskPublisher in
      guard error.networkUnavailableReason == .constrained else {
        throw error
      }

      return URLSession.shared.dataTaskPublisher(for: smallUrl)
  }.tryMap { (data, _) -> UIImage in
    guard let image = UIImage(data: data) else {
      throw NetworkError.invalidData
    }

    return image
    }.eraseToAnyPublisher()
}

The above snippet uses the dataTaskPublisher(for:) method to create a DataTaskPublisher. If this publisher emits an error, the error is caught to see if the error was thrown due to Low Data Mode being enabled. If this is the case, a new DataTaskPublisher is created. This time for the low-quality URL. If the request succeeds, the retrieved data is converted to an image in a tryMap block. If the conversion fails an error is thrown. Note that NetworkError.invalidData is not a built-in error, it’s one that you’d have to define yourself. Lastly, the result of tryMap is converted to an AnyPublisher to make it easier to work with for callers of fetchImage(largeUrl:, smallUrl:).

And that’s it! A complete implementation of low data mode with a fallback in less than 20 lines of code.

Enabling low data mode for an entire URLSession

If you find that supporting low data mode on a case by case basis for your requests is tedious, you can also set up an entire URLSession that restricts network access when low data mode is enabled. I won’t show you how to make requests and implement fallback logic since this is all done identically to the previous example. The only difference is that you should use your own URLSession instance instead of the URLSession.shared instance when you create data tasks. Here’s an example of a URLSession with a configuration that supports low data mode:

var configuration = URLSessionConfiguration.default
configuration.allowsConstrainedNetworkAccess = false
let session = URLSession(configuration: configuration)

With just three lines of code, all of your URL requests (that are made using the custom session) support low data mode! No extra work needed. Pretty rad, right? But what if you want to restrict something else, like for example media playback? No worries, that also works. Let me show you how.

Low data mode for media playback

Media playback is possibly one of the best ways to use heaps of a user’s data in your app. Some apps use video assets as beautiful backgrounds or rely on video for other non-essential tasks. If this sounds like your app, you might want to implement low data mode for media playback. If you’re using AVURLAsset in your app, implementing low data mode is fairly straightforward. All you need to do is set the AVURLAssetAllowsConstrainedNetworkAccessKey on the asset’s options dictionary to false and AVPlayer takes care of the rest. A small snippet for reference:

guard let mediaUrl = URL(string: "https://yourdomain.com/media.mp4")
  else { return }

let asset = AVURLAsset(url: mediaUrl, options: [AVURLAssetAllowsConstrainedNetworkAccessKey: false])

Straightforward and effective, I like it.

Detecting Low Data mode without relying on an error

If you want to be pro-active with your low data mode implementation, and for instance warn a user that they are about to engage in an activity that will potentially use more data than they would like, you can use the Network framework. Apple's Network framework is typically used if you want to perform very low-level networking management that you can't achieve with URLSession. Since URLSession doesn't appear to have any way to detect whether a user has low data mode turned on before making a request, you must fall back to Network to do this kind of detection. The following code is an example of how you can detect that a user has low data mode turned on using the Network framework:

let monitor = NWPathMonitor()

monitor.pathUpdateHandler = { path in
  let constrained = path.isConstrained
}

monitor.start(queue: DispatchQueue.global())

The code above uses an NWPathMonitor to monitor the available networking paths on the device. When a path becomes available or if a path changes, the monitor will call its pathUpdateHandler with the available, recently changed path. The monitor won't work until you call its start method. Depending on your needs, coding styles and conventions you might want to create a dedicated dispatch queue that the monitor will call the update handler on. Also note that you'll want to keep a reference to the NWPathMonitor in the object that is using the monitor to prevent it from being deallocated before its update handler is called.

Thanks to @codeOfRobin for pointing out that NWPath can help with detecting low data mode.

Other considerations to limit unwanted data usage

In addition to giving you the ability to support low data mode, Apple has introduced another way for your app to prevent overuse of data. If you want to limit data usage on cellular networks, mobile hotspots and potentially on other expensive networks, you can use the allowsExpensiveNetworkAccess setting on URLSession and URLRequest or the AVURLAssetAllowsExpensiveNetworkAccessKey key on AVURLAsset to limit expensive data usage. NWPath also has an isExpensive property to detect whether a user is using an expensive network. If you’re currently restricting cellular access, you might want to consider checking for expensive networks instead. After all, cellular networks are improving all the time and maybe someday in the future they might not always be considered expensive anymore.

In conclusion

This post has shown you several ways to support low data mode in your app:

  1. By configuring your URLRequest
  2. By applying a configuration to a URLSession
  3. By configuring your AVURLAsset for low data mode

You also learned how to implement an image loader that falls back to a lower quality image using Combine, which is pretty cool on its own!

It is now up to you to come up with clever fallbacks, and to take a good look at the data you send and receive over the network, and ask yourself whether you truly need to make every request, or maybe you can optimize things a little bit.

As always, feedback, compliments, and questions are welcome. You can find me on Twitter if you want to reach out to me.

Also, thanks to @drarok for helping me make the Swift compiler happy with the Combine part of this post!

Spend less time maintaining your test suite by using the Builder Pattern

Often when we write code, we have to initialize objects. Sometimes the object’s initializer doesn’t take any arguments and a simple let object = MyObject() call suffices to create your object, other times things aren’t so simple and you need to supply multiple arguments to an object’s initializer. If you have read my previous post, Cleaning up your dependencies with protocols , you might have refactored your code to use protocol composition to wrap dependencies up into a single object that only exposes what’s needed to the caller. In this blogpost I would like to show you a technique I use when this kind of composition isn’t an option.

What problem are we solving exactly?

Before I dive in and show you how you can use builders in your test suite, let’s take some time to fully appreciate the problem we’re trying to solve. Imagine you’re building a very simple logger object that has the following initializer:

init(sink: LogSink) {
  self.sink = sink
}

All you need to do to create a logger, is provide it with a log sink and you’re good to go. In your test you might have written several tests for your logger, for instance:

func testLoggerWritesToSuppliedSink() {
  let sink = MockLogSink()
  let logger = Logger(sink: sink)
  logger.log("hello world")
  XCTAssertTrue(sink.contains("hello world")
}

In most applications, you’ll create a single instance of a logger in your AppDelegate, some kind of root coordinator, view model or other top-level object that is responsible for bootstrapping your application. However, in your test suite, you should be creating a new instance of your logger every time you want to test this. You do this to avoid tests accidentally influencing each other. Every test should start with a clean slate.

I want you to keep this in mind and think of the impact that changing the logger’s initializer would have. For instance, what’s the impact of changing the logger’s initializer to the following:

init(sink: LogSink, config: LogConfig) {
  self.sink = sink
  self.config = config
}

Obviously, you’re going to have to update the initializer you’re using in your application code. But you might also have to update dozens of other calls to the Logger initializer in your test suite. You might even have to update several tests that are unrelated to the logger itself, where the logger is used as a dependency for another object.

Personally, when I make changes to my code, I want the impact of that to be minimal. Finding out that there might be dozens of lines of code that I have to update when I change an initializer is not something I enjoy, especially if I can avoid it. This is the exact reason why I’d like to show you the Builder Pattern as it’s a fantastic way to minimize the impact of this kind of problem.

Introducing: the Builder Pattern

If you’ve studied design patterns, the Builder Pattern should be familiar to you. The very short explanation of this pattern is that you use an intermediate object (the builder) to create instances of an object for you so you don’t have to concern yourself with the details of object creation. Besides the use case I will show you in a moment, a builder can have several other benefits, for instance, you could use it to create different versions of an object depending on whether your app is built in debug or release mode.

In the context of testing, I really like to use the builder pattern for two reasons:
1. I don’t have to change a lot of code every time I refactor.
2. Test code is cleaner because it’s not polluted with information about creating an object if it’s not relevant to my test.

Before I explain the pattern and it’s usage a little bit more, let’s have a look at an example builder and how it would be used in context.

// The builder
class LoggerBuilder {
  var sink: LogSink = MockLogSink()
  var config: LogConfig = TestLogConfig()

  func build() -> Logger {
    return Logger(sink: sink, config: config)
  }
}

// in a test class
func testLoggerWritesToSuppliedSink() {
  let loggerBuilder = LoggerBuilder()
  let logger = loggerBuilder.build()
  logger.log("hello world")
  XCTAssertTrue(loggerBuilder.sink.contains("hello world")
}

The code above is a good example of how the builder pattern can hide some of the complexities that you’re unlikely to be interested in while writing your tests. For example, it’s likely that all of your tests will use the exact same configuration object, and it’s very possible that the MockLogSink contains all the functionality you need to use and test your logger in most of your test cases.

To make effective use of this pattern in your tests, there are a couple of rules to keep in mind. First, the builder should always be a reference type with mutable properties. This allows users of the builder to override certain properties on the object that is created. In the case of the logger, you’ll likely want to write a couple of tests that verify that a logger handles certain configurations as expected. You can achieve this by setting the config property on the builder:

let loggerBuilder = LoggerBuilder()
loggerBuilder.config = SpecialConfig()
let logger = loggerBuilder.build()

This brings me to the second rule. A builder must always construct and return a new instance when build() is called. This will make sure that any customizations that are performed by the user of your builder are actually applied.

The third rule of the builder pattern as I use it is that it should do as little work as possible in the build() method. And it’s strictly forbidden to create instances of objects that would be passed to the built object. For example:

// Don't do this!
class ProfileViewModelBuilder {
  var userProfile: UserProfile = MockUserProfile()

  func build() -> ProfileViewModel {
    let logger = LoggerBuilder().build()
    return ProfileViewModel(userProfile: userProfile, logger: logger)
  }
}

// Do this instead
class ProfileViewModelBuilder {
  var userProfile: UserProfile = MockUserProfile()
  var logger = LoggerBuilder().build()

  func build() -> ProfileViewModel {
    return ProfileViewModel(userProfile: userProfile, logger: logger)
  }
}

The fourth and final rule to keep in mind when working with this pattern is that you should always have default values for every property, even if it doesn’t make much sense. This value can never be another builder though, you don’t want to create instances of objects in a builder’s build() method because that would violate rule number three. Of course, you can use builders to create instances as I did in the previous example with the following line of code: var logger = LoggerBuilder().build(). The following code snippet shows an example of using builders the wrong way:

// Don't do this!
class ProfileViewModelBuilder {
  var userProfile: UserProfile = MockUserProfile()
    var loggerBuilder = LoggerBuilder() 

  func build() -> ProfileViewModel {
    let logger = LoggerBuilder().build()
    return ProfileViewModel(userProfile: userProfile, logger: loggerBuilder.build())
  }
}

By using builders instead of concrete objects, you lose the ability to properly configure and control a builder. There might be cases where you want to pass the same instance of a logger to several builders for instance, or you might want to keep a reference to a logger in your test to ensure that a certain object uses the logger correctly. This would be very hard if a builder creates new instances of the logger when you don’t expect it to.

If you keep these four rules in mind while writing builders for your own test suite, you should be able to improve your test suite’s readability and flexibility in no time!

In summary

I hope that this post has given you some insight into the builder pattern and how you can use it to make improvements to your test suite. From my experience, the builder pattern is very powerful for both small and large projects but it does take some getting used to at first. I would like to recommend that you carefully start introducing builders for simple objects in your test suite before refactoring large complicated sections of your test code since that, like any big refactor, can prove to be quite the challenge.

Once you start adopting builders, take a moment to appreciate your test code, you should find that your test code contains less boilerplate, less configuration and setup than it did before, allowing you to focus on what matters, the test itself. The less code you have to look at, the easier it is to understand the purpose of a test and the Builder Pattern is just another tool to achieve this kind of clarity.

I hope this blog post inspires you to make improvements to your test suite. If you have questions, doubts or if you want to let me know that this post helped you, don’t hesitate to reach out on Twitter

Cleaning up your dependencies with protocols

If you’re into writing clean, testable and maintainable code you must have come across the term “Dependency Injection” at some point. If you’re not sure what dependency injection is, that’s okay. I will explain it briefly so we’re all on the same page before we get to the main point of this post.

Dependency Injection in a Nutshell

Dependency injection is the practice of making sure that no object creates or manages its own dependencies. This is best illustrated using an example. Imagine you’re building a login page for an app and you have separate service objects for registering a user and logging them in. Personally I like using an MVVM-ish approach in my apps so I would wrap these services in a LoginPageViewModel. This would leave you with roughly the following code:

// NOTE: this snippet omits parts of the code that I consider non-essential to explaining the practice of Dependency Injection. Copy and pasting this snippet won't work.

class LoginViewController: UIViewController {
  let viewModel: LoginViewModel
}

protocol LoginService {
  func login(_ email: String, password: String) -> Promise<Result<User, Error>>
}
protocol RegisterService {
  func register(_ email: String, password: String) -> Promise<Result<User, Error>>
}

struct LoginViewModel {
  let loginService: LoginService
  let registerService: RegisterService
}

Notice how none of these definitions create instances of their dependencies. This means that the object that is responsible for creating a LoginViewController is also responsible for creating (or obtaining) a LoginViewModel object. And since LoginViewModel depends on two service objects, the object that creates a LoginViewModel must also be able to create (or obtain) the service objects it depends on.

Using dependency injection makes your code more flexible, testable and future proof, and it’s considered good practice amongst practically all areas of software development. However, it has its imperfections and downside, especially in Swift where we don’t really have great support for dependency injection libraries like you might find in, for instance, Java.

Cleaning up your dependencies

As your app grows, you’ll typically find that the number of dependencies that you manage in your application grows too. It’s not uncommon for a single object to (indirectly) depend on a dozen other objects that must be injected into the object’s initializer. This leads to unwieldy initializers which is can be hard to read. On top of this, it makes writing tests more tedious too because if you want to test an object with many dependencies, you’ll have to create an instance of each dependency in your test.

Cleaning up common dependencies is sometimes done by creating one or more dependency containers that might look a bit like this:

struct Services {
  let loginService: LoginService
  let registerService: RegisterService
  let feedService: FeedService
  let shopService: ShopService
  let artistService: ArtistService
  let profileService: ProfileService
}

When using this approach you might inject the entire Services object into the initializer of a LoginViewModel and extract the required services there:

struct LoginViewModel {
  let loginService: LoginService
  let registerService: RegisterService

  init(services: Services) {
    loginService = services.loginService
    regsisterService = services.regsisterService
  }
}

While this solves the problem of having large initializers, it doesn’t solve the problem of having to create many dependencies when you’re writing tests. In fact, this problem is now worse because instead of only having to set up the two services that the LoginViewModel depends on, the entire Services container has to be created. It also breaks encapsulation in some ways because LoginViewModel now has implicit access to all services instead of just the ones that it depends on.

Luckily there is a neat way to get the best of both worlds by composing protocols together and using a typealias. It’s quite simple really, so here’s what I mean.

typealias LoginViewModelServices = LoginService & RegisterService

extension Services: LoginViewModelServices {
  func login(_ email: String, password: String) -> Promise<Result<User, Error>> {
    return loginService.login(email, password: password)
  }

  func register(_ email: String, password: String) -> Promise<Result<User, Error>> {
    return registerService.register(email, password: password)
  }
}

struct LoginViewModel {
  let services: LoginViewModelServices

  init(services: LoginViewModelServices) {
    self.services = services
  }
}

The above code defines a typealias that composes the two dependencies that LoginViewModel has into a single definition. Services is then extended to implement proxy methods for the register and login methods, forwarding them directly to the relevant services, making it conform to LoginViewModelServices and only exposing the methods that are required to make the LoginViewModel work.

This approach neatly wraps up dependencies, making it agnostic of the underlying details of how services are created and managed without exposing too much information to dependent objects. Writing tests for the LoginViewModel is also simplified right now because you can create a single object that conforms to LoginViewModelServices instead of having to create two or more separate objects that might have dependencies of their own.

Of course, this approach might not always work in every context but I’ve personally found that this approached has allowed me to write both cleaner code and cleaner tests. I hope that you’ll find a use for this neat little trick in your own projects and that it allows you to make your code a little bit better just like I have. Don’t hesitate to reach out on Twitter if you have any questions, compliments or other feedback on this post!