Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Testing text of an animated label that appears and disappears

I am struggling to test the appearance of a label(toastLabel) which I have that animates briefly into view when someone enters the wrong email.

private func registerNewUser(email: String, password: String, confirmationPassword: String) {
    if password == confirmationPassword {
        firebaseData.createUser(email: email, password: password, completion: { (error, _ ) in
            if let error = error {
                self.showToast(in: self.view, with: error.localizedDescription)
            } else {
                self.showToast(in: self.view, with: "Registered succesfully")
                self.signInUser(email: email, password: password)
            }
        })
    } else {
        //raise password mismatch error
        print("password mismatch error")
    }
}

func showToast(in toastSuperView: UIView, with text: String) {
    let toastLabel = ToastLabel()
    toastLabel.text = text
    toastSuperView.addSubview(toastLabel)
    layoutToastLabel(toastLabel)
    animateToastLabel(toastLabel)
}

private func layoutToastLabel(_ toastLabel: ToastLabel) {
    toastLabel.centerYToSuperview()
    toastLabel.pinToSuperview(edges: [.left, .right])
}

private func animateToastLabel(_ toastLabel: ToastLabel) {
    UIView.animate(withDuration: 2.5, delay: 0, options: .curveEaseOut, animations: {
        toastLabel.alpha = 0.0
    }, completion: { _ in
        toastLabel.removeFromSuperview()
    })
}

I just want to test that the error text received back from firebase appears after the user enters an email that has already been taken.

func testRegisteringWithUsedEmailDisplaysFirebaseError() {
    let email = registeredEmail
    let password = "password"

    welcomeScreenHelper.register(email: email,
                                 password: password,
                                 confirmationPassword: password,
                                 completion: {

        let firebaseErrorMessage = "The email address is already in use by another account."
        XCTAssert(self.app.staticTexts[firebaseErrorMessage].exists)
    })
}

func register(email: String, password: String, confirmationPassword: String, completion: (() -> Void)? = nil) {
    let emailTextField = app.textFields[AccesID.emailTextField]
    let passwordTextField = app.secureTextFields[AccesID.passwordTextField]
    let confirmPasswordTextField = app.secureTextFields[AccesID.confirmPasswordTextField]
    let registerButton = app.buttons[AccesID.registerButton]

    emailTextField.tap()
    emailTextField.typeText(email)
    passwordTextField.tap()
    passwordTextField.typeText(password)
    registerButton.tap()
    confirmPasswordTextField.tap()
    confirmPasswordTextField.typeText(confirmationPassword)
    registerButton.tap()

    completion?()
}

when I use other tools such as expectation and XCTWaiter the tests still don't pass despite the text and label definitely appearing. I have never had to do a test like this so I'm not sure where I may be going wrong, whether I have to do something different to test an animated view or something.

Update1:

So I can see after a bit more playing that when i tap the registerButton the toast appears as it should but the test doesn't continue until it has disappeared again. I find this odd as it's not strictly attached to the registerButton being its own view.

update2:

I have update my test as follows:

func testRegisteringWithUsedEmailDisplaysFirebaseError() {

    welcomeScreenHelper.register(email: registeredEmail,
                                 password: password,
                                 confirmationPassword: password,
                                 completion: {

        let firebaseErrorMessage = "The email address is already in use by another account."

        let text = self.app.staticTexts[firebaseErrorMessage]
        let exists = NSPredicate(format: "exists == true")

        self.expectation(for: exists, evaluatedWith: text, handler: nil)
        self.waitForExpectations(timeout: 10, handler: nil)
        XCTAssert(self.app.staticTexts[firebaseErrorMessage].exists)
    })
}

with the addition of:

override func setUp() {
    app.launch()
    UIView.setAnimationsEnabled(false)
    super.setUp()
}

override func tearDown() {
    if let email = createdUserEmail {
        firebaseHelper.removeUser(with: email)
    }
    UIView.setAnimationsEnabled(true)
    super.tearDown()
}

But so far no luck. I can still see that in func register once the register button is tapped the toast shows and the next line isn't called until the toastLabel has finished animating.

like image 326
Wazza Avatar asked Aug 25 '17 09:08

Wazza


Video Answer


1 Answers

There are several things you need to solve in such kind of test:

  1. If the code you are testing is using DispatchQueue.async you should use XCTestCase.expectation
  2. If the code you are testing has UIView.animate in it (I see there is one in your example) do UIView.setAnimationsEnabled(false) before the test and enable it back after the test finishes so expectation won't wait for animation to complete. You can do it in XCTestCase.setUp and XCTestCase.tearDown methods.
  3. If the code you are testing has dependencies like services that are doing async calls (I assume firebaseData is) you should either inject their mocks/stubs that will behave synchronously or use XCTestCase.expectation and pray for API/network be OK while the tests are run.

So using XCTestCase.expectation + UIView.setAnimationsEnabled(false) should work for you. Just XCTestCase.expectation with high enough timeout should work too.

EDIT 1: Correct way to use expectation:

func test() {
    let exp = expectation(description: "completion called")
    someAsyncMethodWithCompletion() {
        exp.fulfill()
    }
    waitForExpectations(timeout: 1) { _ in }
    // assert here
}

So your test method should be:

func testRegisteringWithUsedEmailDisplaysFirebaseError() {
    let exp = expectation(description: "completion called")
    welcomeScreenHelper.register(email: registeredEmail,
                                 password: password,
                                 confirmationPassword: password,
                                 completion: { exp.fulfill() })
    self.waitForExpectations(timeout: 10, handler: nil)
    let firebaseErrorMessage = "The email address is already in use by another account."
    XCTAssert(self.app.staticTexts[firebaseErrorMessage].exists)
}
like image 130
Alexey Kozhevnikov Avatar answered Sep 29 '22 12:09

Alexey Kozhevnikov