This question is somewhat similar to Mock third party classes (Firebase) in Swift but different enough to warrant a new question, based on the answers to it.
I'm trying to mock the Auth
/ FIRAuth
method signIn(withEmail email: String, password: String, completion: AuthDataResultCallback?)
and am running into difficulties with trying to mock the AuthDataResultCallback
object, mainly because it has a User
property that I also want to mock. Unfortunately, I'm not able to create my own User
or Auth
objects because they've been marked as not having an available initializer in Swift.
I have an object (let's call it UserAuthenticationRepository
) that's responsible for performing user authentication and database reads. I'd like to inject a Firebase auth object into it to do these things under the hood, but since I want to test this repository object I'd like to be able to inject a Firebase mock object when I go to unit test it.
What I want to do is something like this (simplified slightly for this question):
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()
}
}
As noted above, I'm running into problems when I try to extend AuthDataResult
to conform to my FirebaseAuthDataResultType
protocol. Is it possible to do what I'm trying to do? I'd ultimately like to pass back a uid
string in my Firebase authentication service when testing UserAuthenticationRepository
.
I was eventually able to find a way to mock the Firebase Auth
objects necessary for me, but I had to resort to subclassing the Firebase User
object, adding new properties to it be used during testing (can't create a User
object directly or mutate its properties), and then creating a struct which conforms to FirebaseAuthDataResultType
which is initialized with a MockUser
object during testing. The protocols and extensions I ended up needing are below:
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)
}
}
Below are the mock objects:
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
}
An instance of my mock Firebase authentication service with stubs:
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
}
Usage (using RxSwift
and 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.
}
If anyone has any better ideas of how this can be accomplished, please let me know!
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.