Behavior-Driven Development (BDD) in iOS using Swift — Part 2

Nicolas Nascimento
The Startup
Published in
7 min readMay 9, 2020
Photo by Max Duzij on Unsplash

Continuing from where we stopped

If you are reading this post, it is likely that you read the first part.

Anyhow, here is a refresher on our current status:

  • ✅ Augmenting user stories using BDD scenarios;
  • ✅ Coding high-level features using executable specifications;
  • 🛠 Coding low-level features using low-level specifications;
  • ⏭ Coding high-level UI features using executable specifications;

Items marked with 🛠 will be covered in this part, while items marked with will be implemented in other parts. Items marked with are already implemented.

In this context, we will continue to develop code for our Weather-App.

Coding low-level features using low-level specifications

After implementing the high-level features, which encompass the top-level business rules, we proceed to the low-level code. This is where you would implement features such as persistence, server communication, etc.

In any case, it is important we only implement what has been required by the previous step.

In the case of your example App, we only need to implement a ForecastProvider that communicates with the OpenWeather API and return the forecasts.

To do so, we need to understand the API and its returning value. Taking a look at the docs, we notice that all we need is a city name and the API key.

The API response is a JSON in the following format. Lines in red are drawn for the properties we actually care about.

Now, we can begin working on the implementation. The strategy will be similar to the one applied in the high-level features, with some subtle changes. Thus, we will continue to follow a three-step process:

  1. Write a failing test.
  2. Make the test pass.
  3. Refactor.

Step 1 — Writing a failing test

In this step, since we do not have any scenarios written for us, we can choose how to write tests. In our implementation, we chose to use Specs.

To achieve this, we create a test file OpenWatherForecastProviderTests and write our Spec using Quick & Nimble. To keep the post short, we will only implement this individual specification. It is important to note that you should write tests to handle edge cases as well.

For our test, we will use a common pattern for writing tests, the Arrange-Act-Assert.

Filling in the implementation, we have the following code.

The expected forecast was obtained from a sample response given a query for "London".

Our test tells us that we only need to implement two structs, a Mock (MockSessionProvider) and our actual Weather Provider (OpenWeatherForecastProvider).

Starting with the OpenWeatherForecastProvider, we provide the minimum implementation.

Next, the MockSessionProvider.

Done. Our code compiles and now we have a failing test to implement.

Step 2— Making the test pass

We start our implementation by the OpenWeatherForecastProvider, as it will hold our actual implementation.

The first step is handling the case where the property cityNames is empty, in that case, we simply fail. Otherwise, we can actually perform the query (i.e. call the internal getForecasts).

Now that are are sure we will perform at least one query, we can proceed to build the url.

Starting by the method signature, you will notice an index parameter. This parameter is required so that we can recursively perform as many queries as there are names in the cityNames property. This is required since the API call which accepts a city name can only handle a single city name per request.

Continuing, we implement an individual query. We start by adding a method to the SessionProvider protocol. This method should receive an URL, perform the query and return data resulting from the query.

Next, we use this to continue our implementation of _getForecasts.

Breaking the code in steps, we:

  • Get the data from the URL, using our sessionProvider.
  • Decode it using a Response struct. This is performed so that we can use properties instead of handling keys in a dictionary.
  • Map this response to our Forecast model.

Now, the only missing step is to chose whether to stop performing queries, ending the recursion, or to continue.

Done. The complete code is displayed below.

Moving on, we implement our MockSessionProvider. As it conforms to SessionProvider, we must implement its required methods.

Here, we are simply returning a Future that always succeed. The response is obtained using a sample response provided by the OpenWeather API in a query for "London".

If we run our code now, we still have an error.

The error is related to the precision limit of Double. To ensure our equality comparison works properly, we can use only the first two digits after the comma when comparing. This can be achieved by overring the Equatable protocol in our Forecast model.

Done, now our test passes.

Step 3—Refactoring

Our final step now is to refactor our code. We will only make small adjustments, avoiding over engineering.

The first adjustment is make sure we use URLSession by default on our OpenForecastProvider. To achieve this, we assign the sessionProvider property to URLSession.shared and implement the SessionProvider protocol for URLSession.

Another adjustment is the Response struct. Since the docs state that some properties may not be present, we should use optionals in our properties.

Since we updated our Response and we use it to create our Forecast models, it is a nice moment to add an initializer in Forecast.

Now, we can use it in our _getForecasts implementation.

Done.

Conclusion

This post continued the implementation of the proposed BDD workflow, completing the its third part.

This third part of the workflow provides you with guidance on how to implement low-level features using low-level specifications. Specifically, we have gone through the process of implementing an API request using a low-level specification. To make the post shorter, we have chosen not handle any API erros that would occur.

As such, our current status is:

  • ✅ Augmenting user stories using BDD scenarios;
  • ✅ Coding high-level features using executable specifications;
  • ✅ Coding low-level features using low-level specifications ;
  • ⏭ Coding high-level UI features using executable specifications;

The next parts of the workflow will be covered in upcoming posts. In the meantime, you access the developed code here.

Finally, I hope you find this guide useful and if you want to continue learning more about BDD, sign up for a complete BDD course in iOS.

Thanks.

(EDIT: Part 3 is here).

--

--

The Startup
The Startup

Published in The Startup

Get smarter at building your thing. Follow to join The Startup’s +8 million monthly readers & +772K followers.