For Development and Quality Assurance engineering teams, coding is an interesting & joyful ride. However, when it comes to unit testing it feels like a dark tunnel in that ride.
What is the basic motive of test cases for any Quality Assurance engineering teams? Indeed, a specialized quality engineering to get rid of unwanted crashes, bad code, success and failure of the possibilities for corner cases, which will ultimately lead to a best quality product/application/software and faster go-to-market in this competitive landscape.
Give your engineering team the aura of light with this insightful 5-min read on Unit Test cases in iOS using Swift or Objective-C.
Through this blog, your engineering teams will learn about:
- Demystifying Unit test cases
- Major Uses of Unit Test Cases
- Real scenario examples of writing unit test cases in Swift or Objective-C
- Where to use Mock class
- Effective library usage of OCMock
Demystifying Unit Tests – What is Unit Testing?
A unit test case is a function that validates the piece of code in both successful & failed scenarios. If any code change happens in your application which can fail the test case and if we can easily validate the code change whether being right or wrong, it should never break our existing functionality.
Unit test cases are automated and once we run them, they validate all written test cases for the application. For iOS Development we require XCode. Using the default XCTest framework we can write unit test cases.
What are the Major Uses of Unit Test Cases?
Unit test cases are used majorly for the following:
- Improve Code Quality: Adding test cases in the application allows the developer to know the code bugs, object allocation-deallocation, crashes, success & failure which makes the application robust with minimum bugs.
- Identify Bugs Easily: When the developers cover the code in the unit test cases with all possible end user scenarios, the bugs can be easily identified during the time of development before it goes to quality assurance.
- Avoid App Crash: As engineers check all the null values & object allocation-deallocation, the application crashes can be easily avoided.
- Reduce Bug Fix Cost: Unit test case application with minimum bugs reduces the QA cycles which in turn reduces the testing costs.
- Handle Corner Cases: Occurring outside the normal operating cases, these corner cases though less can be covered in unit testing.
- Cover Different User Scenarios: It covers possible use case scenarios for the same piece of code.
Writing Unit Test Cases in Swift With an Example
Since we demystified unit testing and its major uses, let us move ahead with how to start with writing test cases. We will take a simple example of a function as mentioned below:
Here in this function, we are creating a product with a name, id & price. Also, in this function, we are checking if the “id” is an empty string in which case we are returning the Product as nil. Now to validate this we are going to write unit test cases where we will handle the success & failure of the different cases.
Unit test cases run with the testing target. Always check if you have a unit test target or not, if not, then add it. Then under this target, you need to create a new test case file. You will use this new file for writing test cases.
Always Start Unit Test Cases by Importing
Then Add Testable Import as Below
@testable import “NameOfApplication”
e.g. @testable import FactoryApp
After these imports, we need to set up our class testing object as below
Here CreateProduct function is from the ProductList class so we have created a new test class with the name ProductListTests. Variable “sut” (System Under Test) refers to the testing object for ProductList class. setup() & tearDown() are two important functions for allocation & deallocation of the objects.
Now we will start writing the test case for the createProduct.
Here we write down two test cases to know if the validation we added to create a product is successful or not.
We used XCTAssertNil and XCTAssertNotNil to validate whether the product was created or not. In the same way we can validate the product name, id & price too by using XCTAssertEqual, XCTAssertTrue, XCTAssertFail.
These are the simple ways of writing test cases for individual functions. Here in this function, we had a data binding for a Product but there are some network calls functions as well in the application. We also need to validate them. In such scenarios, we use the concept of Mock Classes.
When we want to write any test case for a completion block, we can use expectation & wait in the test cases. Let us see an example of it.
In the above example, we want to call deleteProduct API where we passed the id to delete the product from the list. As this is an API call, we must wait till the API responds and once the completion block will be called, we can get the result. In such types of test cases, we need to write the expectation & wait till the expectation is fulfilled.
Use of Mock Classes
Mocking of the classes means creating a “fake” version of it. Mocking plays an important role in Network Class, Protocols. When we want to validate API calls in the function rather than calling actual network methods, we need to create a mock class. So, when we write a test case for any API call, we divert these calls to a mock class instead of an actual network class. Even in cases, when we have a protocol method validation, we can create a mock class for that protocol as below.
Here in this example, we have created MockSearchInteractor for PresenterToInteractorProtocol
When any class which will call the fetchallProduct function of this protocol, the call will go to the mock class instead of the actual codebase where we can verify the protocol method too. In this way, we can easily verify the protocol as well as network calls.
Well, we have read the test case writing in Swift classes but when it comes to Objective-C there is a simple way available for us to verify our test cases and that is the OCMock library.
Effective library usage of OCMock
Unlike Swift, we don’t have to Mock classes. Because OCMock Library provides us the mocking of a class. There are different types of mocking available in this library.
- OCMClassMock: It mocks the instance and class methods defined in the class & its superclass.
- OCMStrictClassMock: When a class receives this method call, strict mocks are raised with an exception which was not explicitly expected.
- OCMPartialMock: It behaves the same as an object. The method which is not stubbed is passed to the object.
OCMock also provides a method stub. Method stubbing means specifying to the method about what to return when the method is invoked. Once we stub any method, we need to verify whether that method returns the same value that we stub.
Here in this example, we are verifying the company asset. So, for that, we mocked the ProductData class using the OCMock library.
Here we stubbed the method of ProductData class as we required to invoke that method & also return the expected string. When the verifyCompanyAsset method gets called it is expected to call the method of ProductData which will call the stub method & return the expected string. Then by using OCMVerify we can validate whether the same string has been returned or not.
In Objective-C also, we can use the XCTest framework to validate the test cases. We can use a combination of the OCMock library & XCTest. Using the OCMock library saves our code for mocking the class. Also, we can stub only those methods which we want to invoke which saves time too.
Hope these small examples & explanations gave a little idea on how your engineering teams can write unit test cases in iOS.
Adding test cases in any application makes the application in a better state with fewer bugs. It takes more time for development with test cases but obviously what we get after this is of great quality.
If your engineering teams follow these practices of developing applications with unit tests, a robust product with faster go-to-market is not a farfetched dream.
Today’s products require a modern approach to digital quality assurance. Beyond delivering flawless omnichannel experience, testing should encompass AI/ML, big data, security, multi-cloud, IoT, and BI, as well the various industry ecosystems in which your product participates. Xoriant has developed modern tools, processes, and frameworks to accelerate software release cycles using an integrated approach to testing and development. Our Continuous Quality Engineering platform coupled with a consultative approach ensures we fit within and augment your own quality engineering plans.
Looking to incorporate specialized quality engineering to your software delivery?