As a followup on my previous post about setting up development environment for unit tests on iOS, in this post, I want to briefly explore the mechanics of actually writing tests for iOS in Objective-C (not Swift). It is assumed that you already have setup your environment.
There are three libraries we will use when writing unit tests:
- Expecta – makes assertions easier to ready, similar to AssertJ for Java
- OCMock – mocking framework, similar to Mockito and PowerMock for Java
- XCTest – this is the standard test framework provided by Apple, similar to jUnit in Java
To start the test, you need to import these and extend the XCTestCase class. Test methods MUST begin with the word “test”. XCTest also provides setUp and tearDown functions which are useful for setting up and destroying mocks.
The basic structure of the tests is as follows:
- Given – setup the test
- When – call the code being tested
- Then – verify the results with assertions
The prep work is basically setting up whatever values will be used in the test, the second step is calling the actual function under tests, and third is assertions to check that it is correct. XCTest is used to run the overall test class, individual test methods and do assertions.
One thing to keep in mind is that unit tests should be isolated from the rest of the code. That means that you should only be testing the actual code in the given class, and mocking whatever calls go out to other classes. This allows us to test each part of code independent of each other.
Basic Example with XCTest
Given the following utility class that works with strings:
Here is a basic test using XCTest (I am purposely not using the specialized boolean methods):
Adding Expecta For Nicer Assertions
One of the problems when using XCTest for assertions, especially when comparing values that it is hard to know which is the result of the test and which is the value we are testing against. Expecta is a framework that allows you to write assertions in Objective-C in a more readable fashion, that makes this clear. Here is an example of the same test using Expecta (I am purposely not using the specialized boolean methods) [changed parts are bolded]:
Adding Mocking With OCMock
At this point we got assertions in place which are readable. However, we are still relying on an outside class, NSScanner. Mocking allows us to simulate classes that our code needs to call, and OCMock is one of the more popular libraries for this task.
NOTE: In iOS, mocking actually replaces the code in memory with the code we provide, which is why we need to be careful to put things back the way they were before. In other words, it is important to ALWAYS call the stopMocking method when done working with the mock object.
In our example, we will emulate the NSScanner call in order to test our class. Because NSScanner itself is a singleton, we will need one mock for the singleton and separate mocks for each of our possibly values. If mocks are used by multiple test cases, then mocking and stopping mocks can be done in the setUp and tearDown methods. Otherwise, we can setup and teardown mocks inside individual test cases.
Example below [changed parts are bolded]:
To take things further, OCMock allows us to verify whether a particular function has been called but that is beyond the scope of this article.