Skip to content

V8tr/AutoMockable

Repository files navigation

Articles related to this project


AutoMockable

The project demonstrates how to code generate Swift mocks using Sourcery.

Usage

Annotate the protocol you want to mock:

//sourcery: AutoMockable
protocol HTTPClient {
    func execute(
        request: URLRequest,
        completion: @escaping (Result<Data, Error>) -> Void
    )
}

Then build your Xcode project test target. During the build phase, Sourcery will generate HTTPClient+AutoMockable.generated.swift that is ready for use in your tests.

Sample test

Given CurrenciesAPIService that we want to test:

final class CurrenciesAPIService {
    private let httpClient: HTTPClient

    init(httpClient: HTTPClient) {
        self.httpClient = httpClient
    }

    func allCurrencies(completion: @escaping (Result<[CurrencyDTO], Error>) -> Void) {
        httpClient.execute(request: .allCurrencies()) { result in
            completion(
                result.flatMap { data in Result { try JSONDecoder().decode([CurrencyDTO].self, from: data) }}
            )
        }
    }
}

And a generated HTTPClientMock:

// Generated using Sourcery 1.3.2 — https://github.com/krzysztofzablocki/Sourcery
// DO NOT EDIT
// swiftlint:disable all

import UIKit
@testable import AutoMockable

class HTTPClientMock: HTTPClient {

    //MARK: - execute

    var executeRequestCompletionCallsCount = 0
    var executeRequestCompletionCalled: Bool {
        return executeRequestCompletionCallsCount > 0
    }
    var executeRequestCompletionReceivedArguments: (request: URLRequest, completion: (Result<Data, Error>) -> Void)?
    var executeRequestCompletionReceivedInvocations: [(request: URLRequest, completion: (Result<Data, Error>) -> Void)] = []
    var executeRequestCompletionClosure: ((URLRequest, @escaping (Result<Data, Error>) -> Void) -> Void)?

    func execute(request: URLRequest, completion: @escaping (Result<Data, Error>) -> Void) {
        executeRequestCompletionCallsCount += 1
        executeRequestCompletionReceivedArguments = (request: request, completion: completion)
        executeRequestCompletionReceivedInvocations.append((request: request, completion: completion))
        executeRequestCompletionClosure?(request, completion)
    }

}

The next test verifies that CurrenciesAPIService correctly handles malformed data in response:

import Foundation
import XCTest
@testable import AutoMockable

class CurrenciesAPIServiceTests: XCTestCase {

    let httpClient = HTTPClientMock()
    lazy var sut = CurrenciesAPIService(httpClient: httpClient)

    func test_allCurrencies_withMalformedData_returnsError() throws {
        httpClient.executeRequestCompletionClosure = { _, completion in
            completion(.success(Data())) // <--- Stub malformed data
        }

        var result: Result<[CurrencyDTO], Error>?

        sut.allCurrencies { result = $0 }

        XCTAssertThrowsError(try result?.get())
    }
}

Verify CurrenciesAPIService correctly parses a response with valid payload:

class CurrenciesAPIServiceTests: XCTestCase {
    ...

    func test_allCurrencies_withResponseSuccess_returnsValidData() throws {
        let expected = CurrencyDTO(
            currencyCode: "A",
            country: "B",
            currencyName: "C",
            countryCode: "D"
        )
        let data = try JSONEncoder().encode([expected])
        httpClient.executeRequestCompletionClosure = { _, completion in
            completion(.success(data)) // <--- Stub valid data
        }

        var result: Result<[CurrencyDTO], Error>?

        sut.allCurrencies { result = $0 }

        XCTAssertEqual(try result?.get(), [expected])
    }
}