I'm trying to unit test a class of my own which is calling a method on a third party class:
FIRAuth.auth()?.signInAnonymously() { (user, error) in
//
}
I'm using protocol based dependency injection to achieve this:
protocol FIRAuthProtocol {
func signInAnonymously(completion: FIRAuthResultCallback?)
}
extension FIRAuth: FIRAuthProtocol {}
class MyClass {
private var firAuth: FIRAuthProtocol
init(firAuth: FIRAuthProtocol) {
self.firAuth = firAuth
}
func signIn() {
firAuth.signInAnonymously() { (user, error) in
//
}
}
}
class MockFIRAuth: FIRAuthProtocol {
var signInAnonymouslyCalled = false
func signInAnonymously(completion: FIRAuthResultCallback? = nil) {
signInAnonymouslyCalled = true
}
}
class MyClassSpec: QuickSpec {
override func spec() {
describe("MyClass") {
describe(".signIn()") {
it("should call signInAnonymously() on firAuth") {
let mockFIRAuth = MockFIRAuth()
let myClass = MyClass(firAuth: mockFIRAuth)
expect(mockFIRAuth.signInAnonymouslyCalled).to(beFalse())
myClass.signIn()
expect(mockFIRAuth.signInAnonymouslyCalled).to(beTrue())
}
}
}
}
}
So far so good! Now, I'd like my mockFIRAuth to return an instance of FIRUser. Here's my issue: I can't create an instance of FIRUser myself.
FYI: public typealias FIRAuthResultCallback = (FIRUser?, Error?) -> Swift.Void
If found this great article which explains how to make a method on a third party class return a protocol instead of a type. http://masilotti.com/testing-nsurlsession-input/ Maybe my situation is different than the article's, but here's my shot at this:
I've defined a FIRUserProtocol:
protocol FIRUserProtocol {
var uid: String { get }
}
extension FIRUser: FIRUserProtocol {}
I've updated my FIRAuthProtocol to call the completion handler with FIRUserProtocol instead of FIRUser:
protocol FIRAuthProtocol {
func signInAnonymously(completion: ((FIRUserProtocol?, Error?) -> Void)?)
}
I've updated my FIRAuth extension to support the modified protocol. My newly defined method calls the default implementation of signInAnonymously:
extension FIRAuth: FIRAuthProtocol {
func signInAnonymously(completion: ((FIRUserProtocol?, Error?) -> Void)? = nil) {
signInAnonymously(completion: completion)
}
}
Finally, I've updated MockFIRAuth to support the modified protocol:
class MockFIRAuth: FIRAuthProtocol {
var signInAnonymouslyCalled = false
func signInAnonymously(completion: ((FIRUserProtocol?, Error?) -> Void)? = nil) {
signInAnonymouslyCalled = true
}
}
Now, when I run my test everything comes to a crashing halt:
Thread 1: EXC_BAD_ACCESS (code=2, address=0x7fff586a2ff8)
Please advice!
After renaming the completion argument label in my FIRAuthProtocol's method everything seems to work as expected:
protocol FIRAuthProtocol {
func signInAnonymously(completionWithProtocol: ((FIRUserProtocol?, Error?) -> Void)?)
}
extension FIRAuth: FIRAuthProtocol {
func signInAnonymously(completionWithProtocol: ((FIRUserProtocol?, Error?) -> Void)? = nil) {
signInAnonymously(completion: completionWithProtocol)
}
}
This solves my issue for now, but I'd still like to know why my first attempt was unsuccessful. Does this mean that the two methods with different parameter types in their closures can't be told apart, which was causing my app to crash?
I've finally found an elegant way to solve this.
protocol FIRAuthProtocol {
func signInAnonymously(completion: ((FIRUserProtocol?, Error?) -> Void)?)
}
extension FIRAuth: FIRAuthProtocol {
func signInAnonymously(completion: ((FIRUserProtocol?, Error?) -> Void)? = nil) {
let completion = completion as FIRAuthResultCallback?
signInAnonymously(completion: completion)
}
}
This way, there's no need to alter function names or argument labels.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With