Testing

This is part of the Semicolon&Sons Code Diary - consisting of lessons learned on the job. You're in the react category.

Last Updated: 2024-04-18

React Testing

Testing with the built-in @testing-library/react

Installation bash npm install --save-dev @testing-library/react

// App.test.js
import { render, screen, cleanup} from '@testing-library/react';

//. You probably need an afterEach(cleanup) to reset state
afterEach(cleanup)


// In the components you should use data-testid to identify components

function Hello(props) {
  if (props.name) {
    return <h1 data-testid="testz">Hello, {props.name}!</h1>;
  } else {
    return <span data-testid="tests">Hey, stranger</span>;
  }
}


// Then you can refer to these in tests with `screen.getByTestId("theid")` -- see within
test('renders hello stranger', () => {
  render(<Hello/>);

// Also each test by render(<Component prop={someProp}>
 // A basic assertion is to check for text content in that element
  expect(screen.getByTestId("tests")).toHaveTextContent("Hey, stranger")
  // FYI If you omit the `toHaveTextContent()` you get the full span or whatever
});
// If you want to test clicks
// import { fireEvent } from '@testing-library/react';

test('allows click', () => {
  render(<BigItem />);
  expect(screen.getByTestId("test1").textContent).toBe("Apple")
// get access the the onClick item using testid
  const button = screen.getByTestId('test2')
  // get call with with fireEvent
  fireEvent.click(button)
  expect(screen.getByTestId("test1").textContent).toBe("Poop")
});

// If you want to test typing need userEvent instead of fireEvent

import userEvent from '@testing-library/user-event'

function BigItem() {
  const [text, setText] = React.useState("Apple")

  return (
    <div>
      <h1 data-testid="test1">{text}</h1>
       <button onClick={() => setText("Poop")} data-testid="test2">Dog</button>
       <textarea data-testid="ta1">hi</textarea>
       <p data-foo="bar">hidy hooo</p>
      <summary>Stuff</summary>
    </div>
  )
}

test('allows text entering', () => {
  render(<BigItem />);
  const ta = screen.getByTestId("ta1")
  expect(ta.textContent).toBe("hi")
// I use the {backspace} to get rid of the existing text there
// userEvent.type(textarea, "{backspace}{backspace}fart")
  userEvent.type(ta, "{backspace}{backspace}fart")
// If you want help debugging what you see add the following
  screen.debug()
  expect(ta).toHaveValue("fart")
});


function Async() {
  const [text, setText] = React.useState("before")
  const doSomething = () => {
      setTimeout(() => (
        setText("after")
    ),500 )
  }
  return (
    <div>
       <button onClick={doSomething} data-testid="trigger"></button>
       <p data-testid="result">{text}</p>
    </div>
  )
}


// For async stuff, use an async jest function to begin with

test.only("works for async appearing stuff", async () => {
  render(<Async />)
  const trigger = screen.getByTestId("trigger")
  fireEvent.click(trigger)

// - then use the `findByText()` function -- do not use findByTestId (unless the test Id updates, which would be weird)
// - you will need to await on findByText.
  const result = await screen.findByText("after")
// - lastly you can go the classic getByTestId stuff
  expect(screen.getByTestId("result").textContent).toBe("after")

})


// If you want to use hardcore querySelect (not recommended),

test('hard to reach querySelector stuff', () => {
  // You extract the contain item here
  const { container } = render(<BigItem />);
  // Then you can do querySelector (which is not possible elsewhere)
  const hardToReach = container.querySelector('[data-foo="bar"]')
  expect(hardToReach.textContent).toMatch(/hoo/)
})