使用 lodash.debounce 延迟测试组件失败
Posted
技术标签:
【中文标题】使用 lodash.debounce 延迟测试组件失败【英文标题】:Testing component with lodash.debounce delay failing 【发布时间】:2021-04-09 17:45:38 【问题描述】:我有一个富文本编辑器输入字段,我想用去抖动组件包装它。去抖输入组件如下所示:
import useState, useCallback from 'react';
import debounce from 'lodash.debounce';
const useDebounce = (callback, delay) =>
const debouncedFn = useCallback(
debounce((...args) => callback(...args), delay),
[delay] // will recreate if delay changes
);
return debouncedFn;
;
function DebouncedInput(props)
const [value, setValue] = useState(props.value);
const debouncedSave = useDebounce((nextValue) => props.onChange(nextValue), props.delay);
const handleChange = (nextValue) =>
setValue(nextValue);
debouncedSave(nextValue);
;
return props.renderProps( onChange: handleChange, value );
export default DebouncedInput;
我使用 DebouncedInput 作为 MediumEditor 的包装组件:
<DebouncedInput
value=task.text
onChange=(text) => onTextChange(text)
delay=500
renderProps=(props) => (
<MediumEditor
...props
id="task"
style= height: '100%'
placeholder="Task text…"
disabled=readOnly
key=task.id
/>
)
/>;
MediumEditor 组件做了一些我想测试的卫生工作,例如剥离 html 标签:
class MediumEditor extends React.Component
static props =
id: PropTypes.string,
value: PropTypes.string,
onChange: PropTypes.func,
disabled: PropTypes.bool,
uniqueID: PropTypes.any,
placeholder: PropTypes.string,
style: PropTypes.object,
;
onChange(text)
this.props.onChange(stripHtml(text) === '' ? '' : fixExcelPaste(text));
render()
const
id,
value,
onChange,
disabled,
placeholder,
style,
uniqueID,
...restProps
= this.props;
return (
<div style= position: 'relative', height: '100%' ...restProps>
disabled && (
<div
style=
position: 'absolute',
width: '100%',
height: '100%',
cursor: 'not-allowed',
zIndex: 1,
/>
)
<Editor
id=id
data-testid="medium-editor"
options=
toolbar:
buttons: ['bold', 'italic', 'underline', 'subscript', 'superscript'],
,
spellcheck: false,
disableEditing: disabled,
placeholder: text: placeholder || 'Skriv inn tekst...' ,
onChange=(text) => this.onChange(text)
text=value
style=
...style,
background: disabled ? 'transparent' : 'white',
borderColor: disabled ? 'grey' : '#FF9600',
overflowY: 'auto',
color: '#444F55',
/>
</div>
);
export default MediumEditor;
这就是我测试这个的方式:
it('not stripping html tags if there is text', async () =>
expect(editor.instance.state.text).toEqual('Lorem ipsum ...?');
const mediumEditor = editor.findByProps( 'data-testid': 'medium-editor' );
const newText = '<p><b>New text, Flesk</b></p>';
mediumEditor.props.onChange(newText);
// jest.runAllTimers();
expect(editor.instance.state.text).toEqual(newText);
);
当我运行这个测试时,我得到:
Error: expect(received).toEqual(expected) // deep equality
Expected: "<p><b>New text, Flesk</b></p>"
Received: "Lorem ipsum ...?"
在检查结果之前,我也尝试使用 jest.runAllTimers();
运行测试,但随后我得到:
Error: Ran 100000 timers, and there are still more! Assuming we've hit an infinite recursion and bailing out...
我也尝试过:
jest.advanceTimersByTime(500);
但是测试一直失败,我得到了文本的旧状态。
似乎状态由于某种原因没有改变,这很奇怪,因为在我用 DebounceInput 组件包装它们之前,组件曾经工作并且测试是绿色的。
我拥有 MediumEditor 的父组件有一个方法 onTextChange 应该从 DebounceInput 组件调用,因为这是作为传递的函数DebounceInput 的onChange
属性,但在测试中,我可以看到这种方法永远无法实现。在浏览器中,一切正常,所以我不知道为什么它在测试中不起作用?
onTextChange(text)
console.log('text', text);
this.setState((state) =>
return
task: ...state.task, text ,
isDirty: true,
;
);
在进一步检查时,我可以看到正确的值在测试中一直传递到 DebouncedInput 中的 handleChange。所以,我怀疑在这个测试中 lodash.debounce 存在一些问题。我不确定我是否应该模拟这个函数或者模拟是否带有笑话?
const handleChange = (nextValue) =>
console.log(nextValue);
setValue(nextValue);
debouncedSave(nextValue);
;
这是我怀疑问题出在测试的地方:
const useDebounce = (callback, delay) =>
const debouncedFn = useCallback(
debounce((...args) => callback(...args), delay),
[delay] // will recreate if delay changes
);
return debouncedFn;
;
我尝试过像这样模拟去抖动:
import debounce from 'lodash.debounce'
jest.mock('lodash.debounce');
debounce.mockImplementation(() => jest.fn(fn => fn));
这给了我错误:
TypeError: _lodash.default.mockImplementation is not a function
我应该如何解决这个问题?
【问题讨论】:
试试 jest.advanceTimersByTime(n),n 等于 500,因为 delay=500 我试过了,但测试一直失败,状态似乎因为某种原因没有改变,这很奇怪,因为在我之前该组件可以工作并且测试是绿色的它们用 DebounceInput 组件包裹。 你用什么库来测试? 仅反应测试渲染 如果您mocklodash/debounce
,您的测试是否通过? import debouce from 'lodash/debounce'; // Tell jest to mock this import jest.mock('lodash/debounce'); // Assign the import a new implementation, in this case it's execute the function given to you debouce.mockImplementation(fn => fn);
【参考方案1】:
我猜你正在使用酶(来自道具访问)。
为了测试jest
中一些依赖定时器的代码:
-
标记为开玩笑使用假计时器调用
jest.useFakeTimers()
渲染您的组件
进行更改(这将启动计时器,在您的情况下是状态更改),注意当您从酶更改状态时,您需要调用componentWrapper.update()
使用jest.runOnlyPendingTimers()
推进计时器
这应该可行。
关于测试反应组件的一些旁注:
-
如果您想测试
onChange
的功能,请测试直接组件(在您的情况下为MediumEditor
),没有必要测试整个包装组件测试 onChange 功能
不要从测试中更新状态,这会使您的测试与特定实现高度耦合,证明、重命名状态变量名称,您的组件的功能不会改变,但您的测试会失败,因为他们会尝试更新不存在状态变量的状态。
不要从测试中调用onChange
道具(或任何其他道具)。它使您的测试更具实现意识(=与组件实现高度耦合),实际上它们并没有检查您的组件是否正常工作,例如认为由于某种原因您没有将 onChange
属性传递给输入,您的测试将通过(因为您的测试调用了 onChange
属性),但实际上它不会起作用。
组件测试的最佳方法是像您的用户一样模拟组件上的操作,例如,在输入组件中,模拟组件上的更改/输入事件(这是您的用户在实际应用中执行的操作)类型)。
【讨论】:
以上是关于使用 lodash.debounce 延迟测试组件失败的主要内容,如果未能解决你的问题,请参考以下文章
如何正确使用带有 lodash debounce 的 Vue JS 手表
使用 Lodash debounce 和 React useCallback 输入 onChange 事件