Learn how to use the Quick and Nimble frameworks for testing iOS projects. Follow a small demo application to implement some example tests.
Unit testing helps us to have confidence that our code is behaving the way we expect it to. It helps us to identify issues before we publish our code. Quick is a testing framework and Nimble is a matching framework. They are easy to use and have the advantage that they are very descriptive of what you are testing when compared with XCTest. We’re going to look at how to get started with Quick and Nimble and write a few simple tests.
We’re going to be building upon the “Using MVVM in iOS” tutorial that I wrote. Use this project as your starter project. Remember when using the starter project to change the development team to your own within the general settings.
Open terminal & move into your working directory. Initialize your pods project by:
1pod init
Open the newly created Podfile
within your favourite text editor. Edit your Podfile
so that the WeatherForecastTests
target now looks like this:
1target 'WeatherForecastTests' do 2 inherit! :search_paths 3 pod 'Quick' 4 pod 'Nimble' 5 end
Save the file and return your terminal and your working directory. Run the command:
1pod install
This will install your new frameworks and create a pods project. It will also generate a workspace. You should now use the new workspace when working on your project. So if you’ve opened your project already close it and in open the WeatherForecast.xcworkspace
instead.
Within your tests target create a new group and file by:
WeatherForcastTests
.ModelTests
.CurrentWeatherSpecs
.Within your new file replace the contents with the following:
1import Quick 2 import Nimble 3 @testable import WeatherForecast 4 5 class CurrentWeatherSpecs: QuickSpec { 6 7 }
This imports the Quick and Nimble frameworks and also imports the main target for testing.
Create a new group and file by:
WeatherForcastTests
.StubData
.london_weather_correct.json
.Copy and paste in the contents of the londonWeather.json
file.
Within your CurrentWeatherSpecs.swift
file add the following code:
1override func spec() {
2 var sut: CurrentWeather!
3 //1
4 describe("The 'Current Weather'") {
5 //2
6 context("Can be created with valid JSON") {
7 //3
8 afterEach {
9 sut = nil
10 }
11 //4
12 beforeEach {
13 //5
14 if let path = Bundle(for: type(of: self)
15 ).path(forResource:"london_weather_correct",
16 ofType: "json") {
17 do {
18 let data = try Data(contentsOf: URL(fileURLWithPath: path),
19 options: .alwaysMapped)
20 let decoder = JSONDecoder()
21 decoder.keyDecodingStrategy = .convertFromSnakeCase
22 sut = try decoder.decode(CurrentWeather.self, from: data)
23 } catch {
24 fail("Problem parsing JSON")
25 }
26 }
27 }
28 //6
29 it("can parse the correct lat") {
30 //7
31 expect(sut.coord.lat).to(equal(1))
32 }
33 it("can parse the correct date time") {
34 expect(sut.dt).to(equal(0))
35 }
36 }
37 }
38 }
context
– under what context is the class or structure being tested. In this example we’re supplying valid JSON. You could pass in invalid JSON and ensure it handles this correctly as well.afterEach
– similar to tear down, it is run after each of the tests within this context/scope.beforeEach
– similar to setup, it is run before each of the tests within this context/scope.it
– here we describe what we about to test. It should be a clear statement of what we are testing and we should be able to test it with just a single expectation.expect
– this is the “actual“ test, we expect sut.coord.lat
to equal 1
. Quick & Nimble makes that quite clear by just reading the line of code.If you run these tests (CMD+U) they will fail. Make sure you have selected a simulator or device to run the tests on. You will receive messages such:
expected to equal <1>, got <51.51>
You will also notice in the test navigator (in the left side panel) a useful test name with a failed test symbol next to it:
The_CurrentWeather__Can_be_created_with_valid_JSON__can_parse_the_correct_lat()
This is the reason why I like testing with Quick and Nimble, it’s extremely easy to see what you have tested and what exactly has failed. You may go ahead and fix these tests by supplying the got
values from the error messages into the expect statements.
First lets, create a new group in our tests folder called ViewControllers
. Within this folder create a new Swift file called ViewControllerSpecs.swift
. Finally open up your Main.storyboard
select the ViewController and within the identity inspector (third from left) set the storyboard identifier to ViewController
.
Replace your ViewControllerSpecs.swift
code with snippet below. You will notice that a lot of this is similar to that of testing the model.
1import Quick
2 import Nimble
3 @testable import WeatherForecast
4
5 class ViewControllerSpecs: QuickSpec {
6 override func spec() {
7 var sut: ViewController!
8 describe("The 'View Controller'") {
9 context("Can show the correct labels text") {
10 afterEach {
11 sut = nil
12 }
13 beforeEach {
14 // 1
15 let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)
16 sut = storyboard.instantiateViewController(withIdentifier: "ViewController") as! ViewController
17 _ = sut.view
18 if let path = Bundle(for: type(of: self)
19 ).path(forResource: "london_weather_correct", ofType: "json") {
20 do {
21 let data = try Data(contentsOf: URL(fileURLWithPath: path), options: .alwaysMapped)
22 let decoder = JSONDecoder()
23 decoder.keyDecodingStrategy = .convertFromSnakeCase
24 sut.searchResult = try decoder.decode(CurrentWeather.self, from: data)
25 } catch {
26 fail("Problem parsing JSON")
27 }
28 }
29 }
30 it("can show the correct text within the coord label") {
31 // 2
32 expect(sut.coordLabel.text).to(equal("Lat: 51.51, Lon: -0.13"))
33 }
34 it("can show the correct location label") {
35 expect(sut.locationLabel.text).to(equal("Location: London"))
36 }
37 }
38 }
39 }
40 }
If you run these tests you will notice again that they fail. You’ll notice that the label text is still returning our default Label
text. This is because the labels are updated on the main thread. We can change the expectation use toEventually
instead.
1expect(sut.coordLabel.text).toEventually(equal("Lat: 51.51, Lon: -0.13"))
Your tests should now pass.
The finished example project can be found here.
We’ve learnt how to write some simple tests for your model and view controller using Quick and Nimble. There are a lot more commands that you may found useful when working within your own project, head to the GitHub repo to learn more.