Unit Testing in iOS

Ha Duyen Hoa
5 min readFeb 22, 2019

--

Part 5: Good practices

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]

Writing Unit tests requires a lot of effort as writing the main application. The purpose of writing Unit tests is not to have a compiled and passed unit tests target but to make sure the implementation of class/methods follows the desired design/functionality.

It’s quite easy to write the very first unit tests and it’s also straightforward to write the next tests. However, there are always challenges when the testing scenario is complicated. Here, I will share some of my experiences to make writing UnitTest more easy and efficient.

Naming

The naming convention of test functions and test classes is important.

Test class’s name: should contain the name of the original class. This strategy helps us know which classes have been tested (partially tested) and which classes have not been tested. I usually name the test class by OrginalClassTests

Test function’s name: should indicate clearly what to test. We should also place test functions of the same function together so we can manage them more efficiently.

This technic is simple and does not require any skill to do, but I find it’s really useful.

With the class Translator (.swift) as below:

My test class & test functions are using XCTest are:

If you use Quick to write test, test class can always use the same naming convention. There will be no separated test function like in XCTest but tests are placed into contexts. Then, we can write down the context and use-cases of context in natural language. Something like that:

Modularization

If your application is not well modularized, writing UnitTest will remind you to do it, and sometimes force you to do refactoring in order to make code modularized.

A good application should have reusable modules or maintenance will be a nightmare. Modules should be also isolated. The relationship between modules should be implemented with Dependency Injection instead of Singleton pattern. This is to avoid changing the implementation of a module can break the functionality of other modules, and make sure modules are isolated.

Instead of copy & paste code to re-use in another class/function, we should create a function for that piece of code. This is very important when developing an application. It makes our code clear, short and modularized. Writing test will be also more comfortable.

Test helpers

While writing unit tests, we may face a situation where we have to repeat the same test initialisation or context setup. If it happens, it will be the right time to create test helpers.

For example, I need to load data from json file in each test case in order to prepare test data.

The code to load json is something like that:

let bundle = Bundle(for: MyTestClass.self)
if let path = bundle.path(forResource: “filename”, ofType: “json”) {
let json = try? Data(contentsOf: URL(fileURLWithPath: path))
...
}

As for my main application, I want to make my test target modularized, so I create a helper class containing this function so I can re-use it whenever I want to. What I got is:

Now, instead of repeating that code, I just call JSONTestHelper.jsonData(..)

By writing helpers, we will make the test classes more clear and shorter. By writing helpers, context preparing will be easier. You will see it when your test target gets bigger and test scenario are more complex.

Isolate testing function from running environment

UnitTesting, by definitition, is to test a unit of work. However, mobile applications when running can have different behavior depending on the running environment.

The running environment we face a lot in mobile application testing is network environment. When we run unit testing of function that gets data or sends data to an API (web service), we may not receive the same response for each run of the same test function. Because the server’s response can be changed depending on server state and time. From the point of view of the test function, it should expect that the server will always return appropriate data for each test case that test function defines.

Another issue with network environment is that test suites may take a lot of time to finish, depending on how fast the server’s reaction is. And this is really a big problem when we want to run unit tests in a CI.

Others environments that we can list here are:

  • Localizations of the device: test can fail when the device change language interface
  • Database, local files: test data may not be the same when we start a new test suite because they have been updated by previously executed tests.
  • Dependencies from other modules/classes, or from the singleton object

The solution for all those problems is to isolate tests from running environment. Basically, we can use those technics:

  • Network environment: use network stub or fake URLSession class to precise expected server’s response for each test case (Example, Example2 — NetworkProviderQuickTests)
  • Localization environment: use dependency injection to fix the localization used during a test run
  • Database environment: create test contexts which are able to reset the database to the initial state and ready to run the next test. Call those context setup method before running each test case. (Example)
  • Dependencies from other modules/classes: create fake classes or use mock library (like OCMock for Objective-C)

References to resources of this section:

OHTTPStubs: https://github.com/AliSoftware/OHHTTPStubs

OCMock: http://ocmock.org/

That’s it. Hope this article can give you some idea on how to write an efficient unit test and how to resolve challenges during writing tests.

While you cannot copy-paste codes from images above, you can browse to my sample project in bitbucket to get the original codes: https://bitbucket.org/haduyenhoa/testingfordummies/

I would love to receive your comments and discussions so I can also learn more about this topic. Thanks for reading.

--

--

Ha Duyen Hoa

a Software Engineer who loves audiophile music & traveling.