简体   繁体   中英

Jest / React Testing Library: Is it Possible to Spy on and Wait GraphQL Queries as with Cypress?

Most of my testing experience is with Cypress. I'm attempting to test some React components with React Testing Library (henceforth referred to as "RTL"). I'm wondering if I can apply some of the techniques I use with Cypress.

I have a section of my (web) app which displays a table of search results from a DB. The search API is implemented in GraphQL. We use Apollo on the frontend. Searches can be filtered vis-a-vis a <FilterCheckbox/> component. It includes four checkboxes and a "Clear All" button. The checkbox state is stored in the Apollo cache.

I'm writing a spec for this component. I wish to test that the first two checkboxes are selected by default and that clicking "Clear Filters" deselects them all. I have the following:

import React from 'react'
import FilterCheckboxes from './FilterCheckboxes'
import { render, fireEvent } from 'Utils/test-utils'

import {
  getByText,
  getByTestId
} from '@testing-library/dom'

const props = {
  options: [
    { label: 'new', value: 'FRESH' },
    { label: 'active', value: 'ACTIVE' },
    { label: 'closed', value: 'CLOSED' },
    { label: 'removed', value: 'ARCHIVED' },
  ],
  statusArray: [
    'FRESH',
    'ACTIVE',
  ],
  cacheKey: 'status',
}

describe('FilterCheckboxes component', () => {
  const { container } = render(<FilterCheckboxes {...props} />)

  const checkNew = getByTestId(container,'checkbox_new')
    .querySelector('input[type="checkbox"]')

  const checkActive = getByTestId(container, 'checkbox_active')
    .querySelector('input[type="checkbox"]')

  const checkClosed = getByTestId(container,'checkbox_closed' )
    .querySelector('input[type="checkbox"]')

  const checkRemoved = getByTestId(container, 'checkbox_removed')
    .querySelector('input[type="checkbox"]')

  const clearButton = getByText(container, 'clear status filters')


  it('should render the checkboxes with the correct default state', () => {
    expect(checkNew).toHaveProperty('checked', true)
    expect(checkActive).toHaveProperty('checked', true)
    expect(checkClosed).toHaveProperty('checked', false)
    expect(checkRemoved).toHaveProperty('checked', false)
  })

  it('Should clear all checkboxes when clear filters is clicked', () => {
    fireEvent.click(clearButton)
    setTimeout(() => {
      expect(checkNew).toHaveProperty('checked', false)
      expect(checkActive).toHaveProperty('checked', false)
      expect(checkClosed).toHaveProperty('checked', false)
      expect(checkRemoved).toHaveProperty('checked', false)
    }, 500)
  })
})

The tests pass, but the second one only passes if I do this arbitrary delay of 500ms. Without it, the checkboxes are still in the default state when the expect() calls fire.

If this were a Cypress test and the onClick called a REST API, I would do something like this:

cy.server()
cy.route('POST', '/foo/bar/v2', '@someAliasedFixture').as('fooBar')

cy.get('.my-button')
.click()
.then(() => {
    cy.wait('@fooBar')
    // continue test here as we know that the call completed
})

Is it possible to do something like this with Jest / RTL / GraphQL / Apollo?

Update: Took another look this morning. It appears that waitFor() is the function intended to be used in async scenarios. However, if I do

it('should deselect checkboxes when Clear Filters is clicked', async () => {
  fireEvent.click(clearButton)
  waitFor(expect(checkNew).toHaveProperty('checked', false))
})

It just fails because the checkbox is still selected. Surely there is a way to test this with RTL?

Based on the examples I've looked at, the orthodox approach seems to be to pass the click handler function as a prop and expect() it .toHaveBeenCalled() called.

Is that correct? This seems wrong to me. My component isn't what makes onClick handlers fire in response to click events. React.js makes that happen. Checking that foo() gets called when clicking on <MyComponent onClick={foo}/> isn't testing that my code works. It's testing that React.js works.

SOLVED! Thanks to @flo:

  it('should render the checkboxes with the correct default state', () => {
    expect(checkNew).toBeChecked()
    expect(checkActive).toBeChecked()
    expect(checkClosed).not.toBeChecked()
    expect(checkRemoved).not.toBeChecked()
  })

  it('should deselect checkboxes when Clear Filters is clicked', async () => {
    fireEvent.click(clearButton)
    waitFor(() => {
      expect(checkNew).not.toBeChecked()
      expect(checkActive).not.toBeChecked()
      expect(checkClosed).not.toBeChecked()
      expect(checkRemoved).not.toBeChecked()
    })
  })

I agree, you should test as a real user would, so you should definitely test that the checkbox is in the correct state instead of testing that the click handler was called. This is what promotes react-testing-library.

Are you sure about this?

expect(checkNew).toHaveProperty('checked', false);

I never used this, but reading the docs I'm not sure if this would do the job ( https://jestjs.io/docs/en/expect#tohavepropertykeypath-value ). In fact I don't see how it could work.

Have you consider using https://github.com/testing-library/jest-dom , and in particular https://github.com/testing-library/jest-dom#tobechecked ?

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM