使用React Test Utils测试Select onChange时history.push未触发
Let's break down why your test isn't passing and fix it step by step:
The Core Problem
You created a standalone createMemoryHistory() instance in your test, but the <ComponentList> component uses useHistory() to get the history object provided by the <MemoryRouter> component. These are two completely separate instances—so your mock history.push never gets called because the component is using a different history object entirely.
Solution 1: Pass Your Mocked History to the Router
Instead of using <MemoryRouter>, use the base <Router> component and pass your mocked history instance to it. This ensures the component uses the exact same history object you're mocking:
import { Router } from 'react-router-dom'; import { createMemoryHistory } from 'history'; import { render, act, Simulate } from 'react-dom/test-utils'; import ComponentList from './ComponentList'; it('should call history.push on change component list selected value', () => { // Create and mock the history instance const history = createMemoryHistory(); history.push = jest.fn(); // Render the component with our custom Router + mocked history act(() => { render( <Router history={history}> <ComponentList /> </Router>, container ); }); const selectElement = document.getElementById('components-select'); // Trigger the change event (not select!) for the dropdown act(() => { Simulate.change(selectElement, { target: { value: 'ui-textarea' } }); }); // Verify the state and mock call expect(selectElement.value).toBe('ui-textarea'); expect(selectElement.options[1].selected).toBeTruthy(); // Bonus: Verify the exact path that was pushed expect(history.push).toHaveBeenCalledWith('/component-list/ui-textarea'); });
Solution 2: Mock the useHistory Hook Directly
If you prefer not to swap out the router component, you can mock the useHistory hook itself to return your mocked push function:
import { useHistory } from 'react-router-dom'; import { render, act, Simulate } from 'react-dom/test-utils'; import ComponentList from './ComponentList'; // Mock react-router-dom's useHistory hook jest.mock('react-router-dom', () => ({ ...jest.requireActual('react-router-dom'), // Keep other exports intact useHistory: jest.fn(), })); it('should call history.push on change component list selected value', () => { const mockPush = jest.fn(); // Make useHistory return our mocked push function useHistory.mockReturnValue({ push: mockPush }); act(() => { render( <MemoryRouter> <ComponentList /> </MemoryRouter>, container ); }); const selectElement = document.getElementById('components-select'); act(() => { Simulate.change(selectElement, { target: { value: 'ui-textarea' } }); }); expect(selectElement.value).toBe('ui-textarea'); expect(selectElement.options[1].selected).toBeTruthy(); expect(mockPush).toHaveBeenCalledWith('/component-list/ui-textarea'); });
Key Notes to Remember
- Use
Simulate.changefor dropdowns: React maps the<select>element'sonChangehandler to thechangesynthetic event, not theselectevent. UsingSimulate.selectwon't trigger your handler. - Verify exact arguments: Checking that
history.pushwas called with the correct path (/component-list/ui-textarea) makes your test more robust than just checking if it was called.
内容的提问来源于stack exchange,提问作者Jean

