Skip to content

Instantly share code, notes, and snippets.

@carlosefonseca
Last active September 17, 2020 22:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save carlosefonseca/7e18fbc94127255c1b20600fcdb5edce to your computer and use it in GitHub Desktop.
Save carlosefonseca/7e18fbc94127255c1b20600fcdb5edce to your computer and use it in GitHub Desktop.
Swift Spy with methods described as enums

An experimentation with having the methods of a spy class described as enums and stored in an array for comparison in the test.

I really like this approach because, with multicursors, it's very easy to copy the funcs and turn them into enum cases. One only has to go to each method and the line to append the enum to the call list. Then, in the tests, you can check if all the calls where made with the expected arguments. And there's no giant list of variables in the spy and all arguments are stored and compared. For the tests I was working on this is pretty cool because some methods do several calls, some of them to the same method but with different arguments and this allows full validation.

The only issue I have with this approach is that when the test fails, the error contains the full path of the enum case and it's quite long. If could get it to just output the enum case name and the associated values (without manually creating it), this would be golden.

Sample output of an error, that I separated in lines but in Xcode is all one line:

XCTAssertEqual failed: ("[
    CEFSomethingCore.FakeSomethingGateway.Methods.uploadSomething(data: CEFSpotifyDoubles.Something(id: nil, data: "qwerty")), 
    CEFSomethingCore.FakeSomethingGateway.Methods.fetchSomething(id: "2", offset: 123)
]")
is not equal to ("[
    CEFSomethingCore.FakeSomethingGateway.Methods.uploadSomething(data: CEFSomethingCore.Something(id: Optional("2"), data: "qwerty")), 
    CEFSomethingCore.FakeSomethingGateway.Methods.fetchSomething(id: "2", offset: 123)
]")

I'm comparing the full array but one could do an assert per call, or even check if a call exists in the array without verifying order.

import Foundation
public struct Something: Equatable {
var id: String?
var data: String
public init(id: String? = nil, data: String) {
self.id = id
self.data = data
}
}
public enum Err: Error, Equatable {}
public protocol SomethingGateway {
func fetchSomething(id: String, offset: Int) -> Result<Something, Err>
func uploadSomething(data: Something) -> Result<String, Err>
}
public class FakeSomethingGateway: SomethingGateway {
public var fetchSomethingResult: Result<Something, Err>!
public var uploadSomethingResult: Result<String, Err>!
// START OF FUN PART ******************
public enum Methods: Equatable {
case fetchSomething(id: String, offset: Int)
case uploadSomething(data: Something)
}
public var calls = [Methods]()
// END OF FUN PART ********************
public init() {}
public func fetchSomething(id: String, offset: Int) -> Result<Something, Err> {
calls.append(.fetchSomething(id: id, offset: offset)) // ADD CALL
return fetchSomethingResult
}
public func uploadSomething(data: Something) -> Result<String, Err> {
calls.append(.uploadSomething(data: data)) // ADD CALL
return uploadSomethingResult
}
}
public class SomethingManager {
var gateway: SomethingGateway
public init(gateway: SomethingGateway) {
self.gateway = gateway
}
public func doStuff(something: Something) -> Result<Something, Err> {
let result = gateway.uploadSomething(data: something)
switch result {
case .success(let id):
return gateway.fetchSomething(id: id, offset: 123)
case .failure(let error):
return Result.failure(error)
}
}
}
import Foundation
import XCTest
class SomethingManagerTests: XCTestCase {
var fakeGateway: FakeSomethingGateway!
var manager: SomethingManager!
override func setUp() {
fakeGateway = FakeSomethingGateway()
manager = SomethingManager(gateway: fakeGateway)
}
func test_doStuff() {
fakeGateway.uploadSomethingResult = Result.success("2")
fakeGateway.fetchSomethingResult = Result.success(Something(id: "2", data: "qwerty"))
let expected: Result<Something, Err> = .success(Something(id: "2", data: "qwerty"))
let output = manager.doStuff(something: Something(data: "qwerty"))
let expectedCalls: [FakeSomethingGateway.Methods] = [
.uploadSomething(data: Something(data: "qwerty")),
.fetchSomething(id: "2", offset: 123),
]
XCTAssertEqual(output, expected)
XCTAssertEqual(fakeGateway.calls, expectedCalls)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment