Skip to content

Tests don't fail gracefully when using wait #78

Closed
@RyanAtViceSoftware

Description

@RyanAtViceSoftware
  • react-testing-library version:
    "react-testing-library": "^2.5.1",
  • react version:
    "react": "^16.3.0",
  • node version:
    v8.9.4
  • npm (or yarn) version:
    5.6.0

Relevant code or config:

// Test
import sinon from "sinon"
import { Simulate, wait } from "react-testing-library"
import { client } from "../../../_old/graphql/apolloClient"
import { mountApp } from "../../../test/testBehaviors"

jest.mock("../../../_old/graphql/apolloClient")

afterEach(() => {
  client.resetStore()
})

describe("Given we load the App", () => {
  describe("When data is returned and there is no user ", () => {
    describe("And we enter a valid username and password and click submit", () => {
      it("Then we call the login mutation with correct arguments and are redirected to the search page", async () => {
        const userResolverStub = sinon.stub()

        userResolverStub.onFirstCall().returns(null)

        userResolverStub.onSecondCall().returns({
          id: "fakeId",
          email: "fakeEmail",
          role: "fakeRole",
          firstName: "fakeFirstName",
          lastName: "fakeLastName",
          algoliaSearchApiKey: "fakeAlgoliaSearchApiKey"
        })

        const loginMuationMock = sinon.expectation.create("loginMutationMock")

        loginMuationMock
          .withArgs(sinon.match.any, {
            input: { email: "[email protected]", password: "fakePassword" }
          })
          .once()
          .returns({
            user: {
              id: "fakeId",
              email: "fakeEmail",
              role: "fakeRole",
              firstName: "fakeFirstName",
              lastName: "fakeLastName",
              algoliaSearchApiKey: "fakeAlgoliaSearchApiKey"
            },
            company: {
              hasPurchasing: true
            }
          })

        const mocks = {
          User: userResolverStub,
          Mutation: () => ({
            // This needed to match the name of the mutation in MutationType.js
            login: loginMuationMock
          })
        }

        const { getByTestId } = mountApp({ mocks })

        await wait(() => {
          const emailTextBox = getByTestId("emailTextBox")

          emailTextBox.value = "[email protected]"

          Simulate.change(emailTextBox)
        })

        await wait(() => {
          const passwordTextBox = getByTestId("passwordTextBox")

          passwordTextBox.value = "fakePassword"

          Simulate.change(passwordTextBox)
        })

        await wait(() => {
          const loginForm = getByTestId("loginButton")

          Simulate.submit(loginForm)
        })

        await wait(() => {
          loginMuationMock.verify()
          expect(getByTestId("search-page")).toBeTruthy()
        })
      })
    })
  })
})

// Component
import _ from "lodash"
import React, { Component } from "react"
import { Link, withRouter } from "react-router-dom"
import styled from "styled-components"
import { withLogin } from "../graphql/mutations/loginMutation"
import { ERRORS } from "../../../../errors"
import { Routes } from "../../../../Routes/index"
import Alert from "../../../../sharedComponents/Alert/index"
import BackgroundButton, {
  Colors
} from "../../../../sharedComponents/forms/BackgroundButton"
import Input from "../../../../sharedComponents/forms/Input"
import GuestContainer from "../../../../sharedContainers/GuestContainer/index"

const LoginStyled = styled(GuestContainer)`
  .password-container {
    position: relative;

    .forgot-password-link {
      position: absolute;
      right: 0;
      top: 33px;
      font-size: 10px;
      font-weight: 100;
      text-transform: uppercase;
    }
  }
`
class Login extends Component {
  state = {
    email: "",
    password: ""
  }

  handleChange = (name, value) => this.setState({ [name]: value })

  login = () => {
    const { email, password } = this.state
    this.props
      .mutate({
        variables: { 
          input: { 
            email, 
            password: password + 'changed'  // <==== trying to break the test
          }} 
      })
      .then(() =>
        this.props.history.push(
          _.get(this.props, "location.state.from", Routes.ROOT)
        )
      )
      .catch(({ graphQLErrors }) => {
        switch (graphQLErrors[0].message) {
          case ERRORS.WrongEmailOrPassword:
            Alert.error("Email or password is incorrect")
            break
          default:
            break
        }
      })
  }

  handleSubmit = e => {
    e.preventDefault()
    this.login()
  }

  render() {
    const { email, password } = this.state
    return (
      <LoginStyled header="Welcome Back" subHeader="Your kitchen awaits">
        <form data-testid="login-form" onSubmit={this.handleSubmit}>
          <Input
            data-testid="emailTextBox"
            value={email}
            label="Email"
            onChange={e => this.handleChange("email", e.target.value)}
            autoFocus
          />

          <div className="password-container">
            <Input
              data-testid="passwordTextBox"
              value={password}
              type="password"
              label="Password"
              onChange={e => this.handleChange("password", e.target.value)}
            />

            <Link
              className="forgot-password-link"
              to={`${Routes.RESET}?email=${email}`}
            >
              Forgot password?
            </Link>
          </div>

          <BackgroundButton
            data-testid="loginButton"
            noHorizontalPadding
            color={Colors.PAPAYA}
            type="submit"
            disabled={!(email && password)}
          >
            Login
          </BackgroundButton>
        </form>
      </LoginStyled>
    )
  }
}

export default withLogin(withRouter(Login))

What you did:

I got the above test working and then I wanted to verify that it would break so I changed the password code like this password: password + 'changed' so that the test would fail and I could make sure the error was good and all that.

What happened:

The test failed eventually but it took a really long time and then didn't provided useful error feedback. We were instead getting a timeout error.

Reproduction:

I think this reproduces the issue. This seems to run for a really long time and never provide useful error feedback.

https://codesandbox.io/s/rjozql4jnm

Problem description:

We need the tests to fail fast and report back a useful error.

Suggested solution:

wait should run several times quickly and if it's not able to get a clean run with no errors then it should Promise.reject(error) with the error was thrown by the callback function.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions