Test Driven Development in iOS, SWIFT - Part 2
This blog is a continuation of my first blog on Test Driven Development in iOS. Please do have a look into it to follow up on this. If still, you are lazy for that, then you can download the starter project for this blog UT_Starter2 and get going.
Let us recall our UML diagram for DemoTestsProject that we were building up.
So let us write down test cases.
One important thing that we should keep in mind while Unit Testing is we should always try to test our public methods and properties but avoid testing the private ones.
Wait. What!!! Are you serious dude?
The reason is public methods and properties are what we expose to the outer world and would not want them to go wrong. There is an awesome Stack Conversation regarding this. Please feel free to check it out.
Test cases for PlaceCellDataModel
In order to display a places list, we are using a table view. So referring to our UML diagram, we have a PlaceTableCell
representing our UITableViewCell
class corresponding to the cell. The PlaceTableCell
uses a data model of PlaceCellDataModel
type to link its UI. So we will be testing whether data model is getting its attributes set properly or not.
Navigate to PlaceCellDataModel.swift
and prepare your test class as below:
What we did here?
- We declared the
sut
i.e System Under Test asPlaceCellDataModel
and aplace
variable that we will be passing in theinit()
ofPlaceCellDataModel
. - We wrote down a test function i.e
func testAttributes()
to test our attributes. - We checked for two assertions i.e
XCTAssertEqual
&XCTAssertNotNil
. We have already encountered the first assertion type earlier. The second assertion type checks that our properties shouldn’t be nil, as you won’t be wanting your cells data model to hold nil values even though it could hold an empty string. - In our else block, we have used
XCTFail("")
which is a failed case with a message. As per our code, we are already checking that the attributes should not be nil. If it happens to be nil then it's a failed case.
Got errors. Okay, let us fix that as a part of our GREEN phase of TDD. Ring bells… !! Remember our discussion in the first blog.
Place the below code in PlaceCellDataModel.swift
:
What we did here?
- Nothing fancy. We just initialized our cell data model with our requirements.
Test cases for PlaceListViewModel
So there are the following properties that we need to obviously test for PlaceListViewModel
:
Output properties - numberOfRows, title
Methods - tableCellDataModelForIndexPath(_ indexPath: IndexPath)
Okay, so if you closely look into init()
method of PlaceListViewModel
, you will see that we are passing something called PlaceDataFetcherProtocol
. What is this again?
A slight Dependency Injection Stuff
We need a network service call or any database call to provide us with a list of Place
. Our view model should only be requiring the result of the call i.e list of places or error message. Test cases should be super fast and efficient. We will not be wanting to make real API or Database calls for data. That would be really extensive, won’t it? So a possible solution would be something we call Dependency Injection. So we would define a protocol PlaceDataFetcherProtocol
that would consist method to fetch a list of places. So whenever we mock the Network or Database call, we can prepare a mock class conforming to the same protocol and implement the function to provide us a fake data.
Catch your breath. It will be more clear once we implement the code. :)
Let us write down the mock DataFetcher first.
Navigate to PlaceListViewModelTests
and paste the following code outside the class declaration unless you want one class to be inside another class. ;)
What we did here?
- We created a
StubPlaceDataFetcher
to stub our data fetcher. This confirmsPlaceDataFetcherProtocol
that we will encounter next to provide with mock place list.
Now you will be seeing warnings, let us remove them.
Create a new folder named DataFetcher
and a new Swift file named as DataFetcher
. Place the below code in the file:
What we did here?
We declared a protocol named PlaceDataFetcherProtocol
which has our required method fetchPlaces(completion: ([Place]?,_ errorMessage: String?)->())
to provide us with a places list and any error message to be displayed.
Now let us navigate to PlaceListViewModelTests
and write down test cases for PlaceListViewModel
. Replace PlaceListViewModelTests
class with the following code.
What we did here?
- We will not discuss each of the test case code as they are quite simple and almost identical.
- The most important pointer to note is the way that we use the
StubPlaceDataFetcher
. This approach can be used to mock network as well as database fetching layers.
Still, there are a lot of errors as we have not yet configured our PlaceListViewModel
. Let us navigate to PlaceListViewModel.swift
and place the following code into it.
What we did here?
- In the
init()
method we passed aPlaceDataFetcher
type and we have initialized aviewDidLoad
closure to inform the view model about the view load of the controller. - We then get the places data by calling
getPlacesData()
which in turn calls thedataFetcher
to provide a list of places. It is obvious for you to wonder what is the data source from which place list will be fetched. So for keeping our blog simple, we will fetch the places from a.json
file in our Resources folder i.ePlacesList.json
. - We then configure our
tableDataSource
by callingconfigureTableDataSource()
. - After that, we configure our view model outputs.
Let us prepare the Data Fetcher class. Paste the following code in DataFetcher.swift
:
What we did here?
- As discussed earlier, our fetcher confirmed to the
PlaceDataFetcherProtocol
. - Then we fetch the place list from
PlaceList.json
and process the raw JSON to our desired type.
Now it's time to link our UI and see the results of our toil. Replace code of PlaceListController
and PlaceTableCell
with the following:
The implementation code is pretty straightforward. We just take values from the view models output and populate the table.
Output:
Congrats on the successful unit testing of your module.
What did we learn?
- Writing test cases for business logic.
- Mocking data.
- Mocking data fetching layer.
- Following TDD concepts and building up the code.
Sample Code: You can find the completed project in the Final folder of DemoTests Repo.
I would love to hear from you
You can reach me for any query, feedback, or just want to have a discussion by the following channels:
Please feel free to share with your fellow developers.