Testing View Controllers - Loading Data

Episode #339 | 15 minutes | published on May 11, 2018 | Uses swift-4.1, Xcode-9.3
Subscribers Only
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)
    }
blog comments powered by Disqus