Home > Mobile >  React testing lib not update the state
React testing lib not update the state

Time:03-29

My component:

import React from 'react'

const TestAsync = () => {
  const [counter, setCounter] = React.useState(0)

  const delayCount = () => (
    setTimeout(() => {
      setCounter(counter   1)
    }, 500)
  )
  
return (
  <>
    <h1 data-testid="counter">{ counter }</h1>
    <button data-testid="button-up" onClick={delayCount}> Up</button>
    <button data-testid="button-down" onClick={() => setCounter(counter - 1)}>Down</button>
 </>
    )
  }
  
  export default TestAsync

My test file:

describe("Test async", () => {
  it("increments counter after 0.5s", async () => {
    const { getByTestId, getByText } = render(<TestAsync />);

    fireEvent.click(getByTestId("button-up"));

    const counter = await waitForElement(() => getByTestId("counter"));

    expect(counter).toHaveTextContent("1");
  });
});

After run the test file, I got error said:

Expected element to have text content:                                                          
      1                                                                                             
Received:                                                                                       
      0

I am a little bit confused why I use waitForElement to get the element but why the element still has the old value?

React-testing-lib version 9.3.2

CodePudding user response:

First of all, waitForElement has been deprecated. Use a find* query (preferred: https://testing-library.com/docs/dom-testing-library/api-queries#findby) or use waitFor instead: https://testing-library.com/docs/dom-testing-library/api-async#waitfor

Now, we use waitFor:

waitFor may run the callback a number of times until the timeout is reached.

You need to wrap the assertion statement inside the callback of the waitFor. So that waitFor can run the callback multiple times. If you put the expect(counter).toHaveTextContent('1'); statement outside and after waitFor statement, then it only run once. React has not been updated when assertions run.

Why RTL will run the callback multiple times(run callback every interval before timeout)?

RTL use MutationObserver to watch for changes being made to the DOM tree, see here. Remember, our test environment is jsdom, it supports MutationObserver, see here.

That means when React updates the state and applies the update to the DOM, the changes of the DOM tree can be detected and RTL will run the callback again including the assertion. When the React component states are applied and become stable, the last run of the callback is taken as the final assertion of the test. If the assertion fails, an error is reported, otherwise, the test passes.

So the working example should be:

index.tsx:

import React from 'react';

const TestAsync = () => {
  const [counter, setCounter] = React.useState(0);

  const delayCount = () =>
    setTimeout(() => {
      setCounter(counter   1);
    }, 500);

  return (
    <>
      <h1 data-testid="counter">{counter}</h1>
      <button data-testid="button-up" onClick={delayCount}>
        Up
      </button>
      <button data-testid="button-down" onClick={() => setCounter(counter - 1)}>
        Down
      </button>
    </>
  );
};

export default TestAsync;

index.test.tsx:

import { fireEvent, render, waitFor } from '@testing-library/react';
import React from 'react';
import TestAsync from '.';
import '@testing-library/jest-dom/extend-expect';

describe('Test async', () => {
  it('increments counter after 0.5s', async () => {
    const { getByTestId } = render(<TestAsync />);

    fireEvent.click(getByTestId('button-up'));

    await waitFor(() => {
      const counter = getByTestId('counter');
      expect(counter).toHaveTextContent('1');
    });
  });
});

Test result:

 PASS  stackoverflow/71639088/index.test.tsx
  Test async
    ✓ increments counter after 0.5s (540 ms)

-----------|---------|----------|---------|---------|-------------------
File       | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
-----------|---------|----------|---------|---------|-------------------
All files  |   88.89 |      100 |      75 |   88.89 |                   
 index.tsx |   88.89 |      100 |      75 |   88.89 | 17                
-----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        2.307 s
  • Related