Warning: An update to Demo inside a test was not wrapped in act(…).
最近在寫單元測試,遇到這個神奇的 warning,記錄一下。
情境: 假設有一個元件,點了會更新 state 並呼叫 props.onChange
class 版:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| export default class Demo extends Component {
state = { sum: 0 };
render() {
const { sum } = this.state;
return (
<button
onClick={() => {
this.setState({ sum: sum + 1 });
this.props.onChange(sum + 1);
}}
>
click me
</button>
);
}
}
|
function 版:
1
2
3
4
5
6
7
8
9
10
11
12
| const Demo = ({ onChange }) => {
const [state, setState] = useState(0);
return (
<button onClick={() => {
setState((s) => s + 1)
onChange(state + 1)
}}>
click me
</button>
);
};
|
我們可以這樣寫單元測試,測試 onChange 有被順利觸發:
1
2
3
4
5
6
7
8
9
10
11
| test('should call onChange', () => {
const onChangeSpy = jest.fn();
const wrapper = mount(<Demo onChange={onChangeSpy}/>);
wrapper
.find('button')
.props()
.onClick();
expect(onChangeSpy).toHaveBeenCalledWith(1);
});
|
測試 Class Component,順利的通過了,但到了 function component,遇到以下warning:
1
2
3
4
5
6
7
8
9
10
11
12
| *Warning: An update to Demo inside a test was not wrapped in act(...).
When testing, code that causes React state updates should be wrapped into act(...):
act(() => {
/* fire events that update state */
});
/* assert on the output */*
*This ensures that you're testing the behavior the user would see in the browser. Learn more at https://fb.me/react-wrap-tests-with-act
in Demo (created by WrapperComponent)
in WrapperComponent*
|
原本想說算了,如果沒有要繼續測後續的行為,噴個 warning 也不影響測試
但明明是相同行為,為何 function 版有warning?
問題出在 state
act(...) warning 是在提醒你可能有意料之外的事發生
如果是 state 改變是你預期中的,應該用 act() 包起來,否則在這個操作之後,會有意料之外的更新
正常在瀏覽器執行時,react 底層在 callstack 過程中就把這步做掉了
但是單元測試中,是從外部呼叫元件去改變狀態,並沒經過 react 的 callstack
它要你用 act 把 “會改變state的操作” 包起來
1
2
3
4
5
6
7
8
9
10
11
12
| import { act } from 'react-dom/test-utils';
...
act(() => {
wrapper
.find('button')
.props()
.onClick();
});
...
|
That’s it !