Skip to content

Instantly share code, notes, and snippets.

@jaredsinclair
Last active May 28, 2020 21:48
Show Gist options
  • Save jaredsinclair/926019df365cb33fbd40e9fdb903bf4b to your computer and use it in GitHub Desktop.
Save jaredsinclair/926019df365cb33fbd40e9fdb903bf4b to your computer and use it in GitHub Desktop.
Exposing private members to tests via composition.
import Foundation
/// This class is public, because my library exposes this to the rest of my app.
public class ObligatoryNetworkingClient {
public enum GetEndpoint {
case timeline
case profile(userID: String)
case directMessages
}
/// Nobody has access to **the** request builder.
private var builder = URLRequestBuilder()
/// Nobody has access to **the** response parser.
private var parser = URLResponseParser()
/// I might never, ever bother with writing tests of this `get(...)` method
/// because it's just glue code. It doesn't contain any business logic, just
/// calls into single-purpose utility objects. Testing this method would also
/// require doing really, really gross things to the layout of this class in
/// order to prepare the object for the test, and I don't wanna do that.
public func get(_ endpoint: GetEndpoint, completion: @escaping (Result<Data, Error>) -> Void) {
func finish(with result: Result<Data, Error>) {
DispatchQueue.main.async { completion(result) }
}
let request: URLRequest
do {
request = try builder.buildRequest(for: endpoint)
} catch {
finish(with: .failure(error))
return
}
URLSession.shared.dataTask(with: request) { (d, r, e) in
do {
let data = try self.parser.parse(response: r, data: d, error: e)
finish(with: .success(data))
} catch {
finish(with: .failure(error))
}
}.resume()
}
}
extension ObligatoryNetworkingClient {
/// This struct could be either `public` or `internal`, it doesn't matter.
/// Either way, unit tests can see it, and consumers of my library can't
/// interfere with the operation of `ObligatoryNetworkingClient` because
/// it's `builder` property is `private`.
internal struct URLRequestBuilder {
internal var accountID: String?
internal var authenticationToken: String?
internal var applicationSecret: String?
internal enum Error: Swift.Error {
case missingAuthenticationToken
case etcetera
}
/// Value in, value out. Unit test the living **shit** out of this method.
internal func buildRequest(for endpoint: GetEndpoint) throws -> URLRequest {
// Insert a shit ton of logic here.
throw URLRequestBuilder.Error.missingAuthenticationToken
}
}
}
extension ObligatoryNetworkingClient {
/// This struct could be either `public` or `internal`, it doesn't matter.
/// Either way, unit tests can see it, and consumers of my library can't
/// interfere with the operation of `ObligatoryNetworkingClient` because
/// it's `parser` property is `private`.
internal struct URLResponseParser {
internal enum Error: Swift.Error {
case requestTimedOut
case etcetera
}
/// Value in, value out. Unit test the living **shit** out of this method.
internal func parse(response: URLResponse?, data: Data?, error: Swift.Error?) throws -> Data {
// Insert a shit ton of logic here.
throw URLResponseParser.Error.requestTimedOut
}
}
}
// MEANWHILE, IN A UNIT TEST.........
let builder = URLRequestBuilder(
accountID: "abc123",
authenticationToken: nil,
applicationSecret: "shhhhh")
do {
_ = try builder.buildRequest(for: .timeline)
XCTFail("This should have failed.")
} catch ObligatoryNetworkingClient.URLRequestBuilder.Error.missingAuthenticationToken {
// Hooray!
} catch {
XCTFail("Wrong error: \(error)")
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment