ios - 模拟 iOS Firebase Auth 登录方法

标签 ios swift firebase unit-testing firebase-authentication

这个问题有点类似于Mock third party classes (Firebase) in Swift但差异足以根据对它的答案提出一个新问题。

我正在尝试模拟 Auth/FIRAuth 方法 signIn(withEmail email: String, password: String, completion: AuthDataResultCallback?) 并在尝试模拟 AuthDataResultCallback 对象时遇到困难,主要是因为它有一个 User 属性,我想模拟。不幸的是,我无法创建自己的 UserAuth 对象,因为它们在 Swift 中被标记为没有可用的初始化程序。

我有一个对象(我们称它为 UserAuthenticationRepository)负责执行用户身份验证和数据库读取。我想向其中注入(inject)一个 Firebase 身份验证对象以在后台执行这些操作,但由于我想测试此存储库对象,因此我希望能够在我对其进行单元测试时注入(inject)一个 Firebase 模拟对象。

我想做的是这样的(针对这个问题稍微简化):

import FirebaseAuth

protocol FirebaseUserType {
    var uid: String { get }
}

extension User: FirebaseUserType {}

protocol FirebaseAuthDataResultType {
    var user: FirebaseUserType { get }
}

extension AuthDataResult: FirebaseAuthDataResultType {
    var user: FirebaseUserType {
        // This is where I'm running into problems because AuthDataResult expects a User object, 
        // which I also use in the UserAuthenticationRepository signIn(withEmail:) method
    }
}

protocol FirebaseAuthenticationType {
    func signIn(withEmail email: String, password: String, completion: ((FirebaseAuthDataResultType?, Error?) -> Void)?)
}

extension Auth: FirebaseAuthenticationType {
    func signIn(withEmail email: String, password: String, completion: ((FirebaseAuthDataResultType?, Error?) -> Void)?) {
        let completion = completion as AuthDataResultCallback?
        signIn(withEmail: email, password: password, completion: completion)
    }
}

protocol UserAuthenticationType {
    func loginUser(emailAddress: String, password: String) -> Observable<User>
}

class UserAuthenticationRepository: UserAuthenticationType {
    private let authenticationService: FirebaseAuthenticationType
    private let disposeBag = DisposeBag()

    init(authenticationService: FirebaseAuthenticationType = Auth.auth()) {
        self.authenticationService = authenticationService
    }

    func loginUser(emailAddress: String, password: String) -> Observable<User> {
        return .create { [weak self] observer in
            self?.authenticationService.signIn(withEmail: emailAddress, password: password, completion: { authDataResult, error in
                if let error = error {
                    observer.onError(error)
                } else if let authDataResult = authDataResult {
                    observer.onNext(authDataResult.user)
                }
            })
            return Disposables.create()
        }
    }

如上所述,当我尝试扩展 AuthDataResult 以符合我的 FirebaseAuthDataResultType 协议(protocol)时遇到了问题。有可能做我想做的事吗?我最终想在测试 UserAuthenticationRepository 时在我的 Firebase 身份验证服务中传回一个 uid 字符串。

最佳答案

我最终找到了一种方法来模拟我需要的 Firebase Auth 对象,但我不得不诉诸子类化 Firebase User 对象,添加新属性在测试期间使用它(不能直接创建 User 对象或改变其属性),然后创建一个符合 FirebaseAuthDataResultType 的结构,该结构用 初始化code>MockUser 测试期间的对象。我最终需要的协议(protocol)和扩展如下:

protocol FirebaseAuthDataResultType {
    var user: User { get }
}

extension AuthDataResult: FirebaseAuthDataResultType {}

typealias FirebaseAuthDataResultTypeCallback = (FirebaseAuthDataResultType?, Error?) -> Void

protocol FirebaseAuthenticationType {
    func signIn(withEmail email: String, password: String, completion: FirebaseAuthDataResultTypeCallback?)
    func signOut() throws
    func addStateDidChangeListener(_ listener: @escaping AuthStateDidChangeListenerBlock) -> AuthStateDidChangeListenerHandle
    func removeStateDidChangeListener(_ listenerHandle: AuthStateDidChangeListenerHandle)
}

extension Auth: FirebaseAuthenticationType {
    func signIn(withEmail email: String, password: String, completion: FirebaseAuthDataResultTypeCallback?) {
        let completion = completion as AuthDataResultCallback?
        signIn(withEmail: email, password: password, completion: completion)
    }
}

下面是模拟对象:

class MockUser: User {
    let testingUID: String
    let testingEmail: String?
    let testingDisplayName: String?

    init(testingUID: String,
         testingEmail: String? = nil,
         testingDisplayName: String? = nil) {
        self.testingUID = testingUID
        self.testingEmail = testingEmail
        self.testingDisplayName = testingDisplayName
    }
}

struct MockFirebaseAuthDataResult: FirebaseAuthDataResultType {
    var user: User
}

我的带有 stub 的模拟 Firebase 身份验证服务实例:

class MockFirebaseAuthenticationService: FirebaseAuthenticationType {
    typealias AuthDataResultType = (authDataResult: FirebaseAuthDataResultType?, error: Error?)
    var authDataResultFactory: (() -> (AuthDataResultType))?

    func signIn(withEmail email: String, password: String, completion: FirebaseAuthDataResultTypeCallback?) {
      // Mock service logic goes here
    }

    // ...rest of protocol functions
}

用法(使用RxSwiftRxTest):

func testLoginUserReturnsUserIfSignInSuccessful() {
    let firebaseAuthService = MockFirebaseAuthenticationService()
    let expectedUID = "aM1RyjpaZcQ4EhaUvDAeCnla3HX2"
    firebaseAuthService.authDataResultFactory = {
        let user = MockUser(testingUID: expectedUID)
        let authDataResult = MockFirebaseAuthDataResult(user: user)
        return (authDataResult, nil)
    }

    let sut = UserSessionRepository(authenticationService: firebaseAuthService)
    let userObserver = testScheduler.createObserver(User.self)

    sut.loginUser(emailAddress: "john@gmail.com", password: "123456")
       .bind(to: userObserver)
       .disposed(by: disposeBag)

    testScheduler.start()

    let user = userObserver.events[0].value.element as? MockUser

    // Assert MockUser properties, events, etc.
}

如果有人对如何实现这一点有任何更好的想法,请告诉我!

关于ios - 模拟 iOS Firebase Auth 登录方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60146503/

相关文章:

ios - 在 iOS 10 中更改可访问性特征语言

ios - 无法设置 CEMarker 的碰撞优先级

objective-c - 你如何在 Swift 中满足 CTFrameGetLineOrigins() 中的 'lineOrigins' 参数?

swift - Swift 中 Firebase 的帖子中的数据结构问题

ios - Firebase 在 Swift 3 中处理其中包含字典的子快照

java - Firebase 响应 400

android - 在react-native中裁剪后的图像非常小

ios - 包含行数据的 Alamofire POST 字典

ios - 如何测试内部调用 NSDate() 的方法?

ios - 在每次迭代之间延迟循环