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) }