Mock third party classes (Firebase) in Swift

半城伤御伤魂 提交于 2020-06-11 18:36:46

问题


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!

Update

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?


回答1:


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.



来源:https://stackoverflow.com/questions/40561597/mock-third-party-classes-firebase-in-swift

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!