Mocking iOS Firebase Auth методы входа - PullRequest
0 голосов
/ 10 февраля 2020

Этот вопрос чем-то похож на Макет сторонних классов (Firebase) в Swift , но достаточно отличается, чтобы оправдать новый вопрос, основываясь на ответах на него.

Я пытаюсь смоделировать Auth / FIRAuth метод signIn(withEmail email: String, password: String, completion: AuthDataResultCallback?) и сталкиваюсь с трудностями при попытке смоделировать объект AuthDataResultCallback, главным образом потому, что у него есть свойство User, которое я также хочу смоделировать , К сожалению, я не могу создавать свои собственные User или Auth объекты, потому что они помечены как не имеющие доступного инициализатора в Swift.

У меня есть объект (назовем его UserAuthenticationRepository) который отвечает за проверку подлинности пользователя и чтение базы данных. Я хотел бы внедрить в него объект аутентификации Firebase, чтобы сделать это под капотом, но, поскольку я хочу протестировать этот объект репозитория, я хотел бы иметь возможность внедрять фиктивный объект Firebase, когда я go для юнит-теста это.

Что я хочу сделать, это что-то вроде этого (немного упрощено для этого вопроса):

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 протоколу. Можно ли сделать то, что я пытаюсь сделать? В конечном итоге я хотел бы передать строку uid в моей службе аутентификации Firebase при тестировании UserAuthenticationRepository.

1 Ответ

0 голосов
/ 17 февраля 2020

В итоге я смог найти способ высмеять необходимые мне объекты Firebase Auth, но мне пришлось прибегнуть к созданию подкласса объекта Firebase User, добавив в него новые свойства, которые будут использоваться во время тестирования (не могу непосредственно создайте объект User или измените его свойства), а затем создайте структуру, соответствующую FirebaseAuthDataResultType, которая инициализируется объектом MockUser во время тестирования. Ниже перечислены протоколы и расширения, в которых я нуждался:

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
}

Экземпляр моей фиктивной службы аутентификации 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.
}

Если у кого-нибудь есть идеи о том, как это можно сделать, пожалуйста, дайте мне знать!

...