这个问题有点类似于Mock third party classes (Firebase) in Swift但差异足以根据对它的答案提出一个新问题。
我正在尝试模拟 Auth
/FIRAuth
方法 signIn(withEmail email: String, password: String, completion: AuthDataResultCallback?)
并在尝试模拟 AuthDataResultCallback
对象时遇到困难,主要是因为它有一个 User
属性,我也想模拟。不幸的是,我无法创建自己的 User
或 Auth
对象,因为它们在 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
}
用法(使用RxSwift
和RxTest
):
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/