Unit Testing in iOS

Ha Duyen Hoa
5 min readNov 17, 2018

--

Part 3: Quick & Nimble for a clean test

Preface

I’m planning to write a complete series of Unit Testing for iOS. You can find finished parts in the links below. Happy reading :)

Part 1: Why do we need Unit Testing when developing an application?

Part 2: My first Unit Tests

Part 3: Quick/Nimble for a clean test

Part 4: How to deal with context

Part 5: Good practices

Part 6: Unit Test, Jenkins & SonarQube altogether [TO BE WRITTEN]

With XCTest framework, you can already write every unit test possible for your application. However, once you do a lot of test case, and have multiple contexts in your test class, you will find out that using XCTest is not so cool. And you will probably interest in using Quick & Nimble. This two frameworks (mainly Quick) will help you organize the tests in your test class, to make it more clear, easier to read and easier to have multiple test contexts in one test class. With Quick, you will feel more comfortable to write the tests for async function.

The simplest way to have Quick & Nimble integrated into your project is by using cocoa pods or dependencies management like Carthage or even Swift Package Manager (Quick & Nimble support those 3 methods).

If go with CocoaPods, run pod init in the terminal at the root folder of your project to init Cocoa Pods for your project. Then using any text editor to add Quick & Nimble like this:

Then finally, run pod install or pod update to install those frameworks. Once it’s done, open the .xcworkspace of your project instead of .xcodeproject.

First tests with Quick

Consider having a Translator class that gets a localized string from a given key. This Translator is able to handle multiple languages and returns the right text at the selected language.

If we use XCTest to write unit testing for that class, we have something like:

import XCTestclass TranslatorXCTests: XCTestCase {
override func setUp() {
//this function is useful when we have 1 test context, or we can use it to initialize common context
}

func testSetupTranslator() {
let translator = Translator()
let yesButton = translator.message("yes_button")

XCTAssertNotNil(yesButton, "Should be able to get a valid localized string")
XCTAssertEqual(yesButton, "Yes", "Should use english as default language")
}

func testTranslationInFrench() {
let translator = Translator()
translator.setUp("fr")

let yesButton = translator.message("yes_button")
let noButton = translator.message("no_button")

XCTAssertNotNil(yesButton, "Should be able to get a valid localized string")
XCTAssertNotNil(noButton, "Should be able to get a valid localized string")
XCTAssertEqual(yesButton, "Oui", "Translation for yesButton in French is 'Oui'")
XCTAssertEqual(noButton, "Non", "Translation for yesButton in French is 'Non'")
}
}

And when we write it with Quick, it will be

import Quick
import Nimble
@testable import TestingForDummies
class TranslatorTests: QuickSpec {
override func spec() {
describe("a Translator") {
var translator: Translator?

context("being initialized") {
beforeEach {
translator = Translator()
}

it("should be able to initialize correctly") {
expect(translator).notTo(beNil())
}

it("should use english as default language") {
expect(translator?.message("yes_button")) == "Yes"
}
}

context("translation to french") {
beforeEach {
translator = Translator()
translator?.setUp("fr")
}

it("should return french text") {
let yesButtonTitle = translator?.message("yes_button")
expect(yesButtonTitle) == "Oui"

let noButtonTitle = translator?.message("no_button")
expect(noButtonTitle) == "Non"
}
}
}
}
}

You will see that in Quick test class, we may have to write more codes. However, those extra codes make the test cases well organized and very clear. When a test is failing, we can see immediately why it fails and in which context. So, it’s not only helpful to write test but also helpful when reading it and maintain it.

When you run those Quick test, you will see in the test report that, all it("should do something") {} are translated to a test case with the full message. So, usually, the structure of the test class is

describe("given a") {
context("when b") {
beforeEach {
//prepare context
}

it("should do/ave c") {
expect(true).to(beTrue())
}
}
}

By using Nimble together with Quick, we can make the assertion check more natural with expect() statement.

The block beforeEach {} make sure before each test (given by an it() {} block), some codes are executed to prepare the context.

Async test with Quick/Nimble

Writing test of async function is even more easy with Quick/Nimble. Instead of having to create the expectation and call the fullfill() when the expectation is satisfied, we just call the expect() statement with eventually assert method. When using this, test case until it get the expected data or the waiting is timed out.

import Quick
import Nimble
class NetworkProviderQuickTests: QuickSpec {
override func spec() {
describe("a NetworkProvider") {
context("when service is up and running") {
var downloadedData: Data?
beforeEach {
//here, we should use SubClass/Mock and Stubs to remove dependence to network call.
}

it("should be able to get server config data correctly") {
NetworkProvider.getServerConfigData({ data in
downloadedData = data
})

expect(downloadedData).toNotEventually(beNil())
}
}
}
}
}

Quick & XCTest in same project

Now, if you start to like Quick, you can wonder if we should write/re-write your test with only Quick or not. My recommendation is: “use both of them”. If you write test for small class that don’t have async code, you can write in XCTest, and for another classes or for async methods, use Quick/Nimble. Of course, Quick is also perfect for small class, but by using XCTest in those cases, you may save sometime to write Unit Testing.

To learn more about Quick/Nimble, you can take a look at the github of that framework:

You can find also code in examples of this post in my git: https://bitbucket.org/haduyenhoa/testingfordummies

Hope you have enjoyed your reading. See you again in next post about Mock, SubClass and Stubs technique in Unit Testing.

Part 4 is ready here: https://medium.com/@haduyenhoa/unit-testing-in-ios-3259deb81694

--

--

Ha Duyen Hoa

a Software Engineer who loves audiophile music & traveling.