testing-library / react-testing-library

🐐 Simple and complete React DOM testing utilities that encourage good testing practices.

Home Page:https://testing-library.com/react

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Hook testing: useReducer doesn't updating state when the reducer action is executed

jpbnetley opened this issue · comments

  • @testing-library/react version: 15.0.7
  • Testing Framework and version: jest 29.7.0, ts-jest: 29.1.2,
  • DOM Environment: jest-environment-jsdom: 29.7.0

Relevant code or config:

describe('Reducer operations', () => {
  test('add operation', () => {
    const PAYLOAD = 'test';
    const { result } = renderHook(() => useReducer(stringReducer, []));
    const [states, dispatch] = result.current;

    act(() => {
      dispatch({ actionType: 'add', payload: PAYLOAD });
    });

    expect(states.length).toBe(1);
    expect(states.at(0)).toBe(PAYLOAD);
  });

  test('remove operation', () => {
    const INITIAL_STATE = ['test', 'remove', 'remaining'];
    const REMOVED_STATE = 'remove';

    const { result } = renderHook(() =>
      useReducer(stringReducer, INITIAL_STATE)
    );
    const [states, dispatch] = result.current;

    act(() => {
      dispatch({ actionType: 'remove', payload: REMOVED_STATE });
    });

    expect(states.length).toBe(2);
    expect(states.some((name) => name === REMOVED_STATE)).toBe(false);
  });
});

What you did:

Created a reducer that takes in an add operation and remove operation.
Wrote tests to validate the state changes after the reducer's dispatch action is executed.

What happened:

The test always return the initial state.

Reproduction:

https://stackblitz.com/edit/vitejs-vite-pwvmkz?file=src%2Fhooks%2Freducer.test.ts

Problem description:

I expect that when the reducer is called with the action, the state updates as the action gets executed.
And then the test assertion is checked after the state of the reducer is updated.
In react terms, after the "render".

Suggested solution:

I think the issue is maybe that reducer test is executed before the state update is complete.
But this is just a wild guess.

const [states, dispatch] = result.current;

You need to destructure this again after the update. You updated the state, so the state returned from useReducer is a new object

I see, after updating the code to your suggestion, it works as expected.

describe('Reducer operations', () => {
  test('add operation', () => {
    const PAYLOAD = 'test';
    const { result } = renderHook(() => useReducer(stringReducer, []));
    
    act(() => {
      const dispatch = result.current[1];
      dispatch({ actionType: 'add', payload: PAYLOAD });
    });

    const [states] = result.current;
    expect(states.length).toBe(1);
    expect(states.at(0)).toBe(PAYLOAD);
  });

  test('remove operation', () => {
    const INITIAL_STATE = ['test', 'remove', 'remaining'];
    const REMOVED_STATE = 'remove';

    const { result } = renderHook(() =>
      useReducer(stringReducer, INITIAL_STATE)
    );
    

    act(() => {
      const dispatch = result.current[1];
      dispatch({ actionType: 'remove', payload: REMOVED_STATE });
    });

    const [states] = result.current
    expect(states.length).toBe(2);
    expect(states.some((name) => name === REMOVED_STATE)).toBe(false);
  });
});

Thank you very much for the quick reply.