Episode #339

Testing View Controllers - Loading Data

Series: Testing iOS Applications

15 minutes
Published on May 11, 2018

This video is only available to subscribers. Get access to this video and 506 others.

Testing view controllers can sometimes be challenging. In this episode we will write some tests that verify a view controller loads its data properly from the API client. We will add additional tests to verify that a loading indicator is shown.

Testing our View Controller with a Mock API Client

import Foundation
import XCTest
@testable import CoinList

class MockCryptoClient : CryptoCompareClient {
    var fetchCallCount: Int = 0
    var completeWithResult: ApiResult<CoinList>?
    var delay: TimeInterval = 1
    var fetchExpectation: XCTestExpectation?

    init(completingWith result: ApiResult<CoinList>? = nil) {
        super.init(session: URLSession.shared)
        completeWithResult = result
    }

    override func fetchCoinList(completion: @escaping (ApiResult<CoinList>) -> Void) {
        fetchCallCount += 1

        guard let completeWithResult = self.completeWithResult else { return }

        fetchExpectation = XCTestExpectation(description: "coin list retrieved")

        DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
            completion(completeWithResult)
            self.fetchExpectation?.fulfill()
        }
    }

    func verifyFetchCalled(file: StaticString = #file, line: UInt = #line) {
        XCTAssert(fetchCallCount == 1, "Fetch call count was not called", file: file, line: line)
    }
}

Testing the view controller fetches data when first loaded

class CoinListViewControllerTests : XCTestCase {

    var viewController: CoinListViewController!

    override func setUp() {
        super.setUp()

        viewController = CoinListViewController.makeFromStoryboard()
    }

    func testFetchesCoinsWhenLoaded() {
        let mockClient = MockCryptoClient()
        viewController.cryptoCompareClient = mockClient
        _ = viewController.view
        mockClient.verifyFetchCalled()
    }

    private func emptyCoinList() -> CoinList {
        return CoinList(response: "",
                 message: "",
                 baseImageURL: URL(string: "http://foo.com")!,
                 baseLinkURL: URL(string: "http://foo.com")!, data: CoinList.Data(coins: []))
    }
}

Testing Loading Indicators

    func testShowsLoadingIndicatorWhileFetching() {
        let mockClient = MockCryptoClient()
        viewController.cryptoCompareClient = mockClient
        _ = viewController.view
        XCTAssert(viewController.activityIndicator.isAnimating)
    }

    func testLoadingIndicatorHidesWhenFetchCompletes() {
        let coinList = emptyCoinList()
        let mockClient = MockCryptoClient(completingWith: .success(coinList))
        viewController.cryptoCompareClient = mockClient
        _ = viewController.view
        wait(for: [mockClient.fetchExpectation!], timeout: 3.0)
        XCTAssertFalse(viewController.activityIndicator.isAnimating)
    }

This episode uses Swift 4.1, Xcode 9.3.