React 测试库适用于 HTML 个元素,但不适用于 React 组件
React Testing Library works on HTML elements but not with React components
基本上,我正在尝试测试具有 select.
的组件
尝试测试组件时,测试失败,返回的是默认值而不是更改后的值。
但是当我使用渲染组件的 HTML 时(来自 screen.debug())它起作用了。
组件:
export function SelectFile({
fileList,
handleChange,
selected,
}) {
return (
<select
className="bg-slate-600 rounded w-auto"
onChange={onChange}
value={selected}
>
<option value="">Select an option</option>
<TodayOptions />
<AllOptions />
</select>
);
function AllOptions() {
return (
<>
{Object.entries(groups).map(([key, value]) => {
return (
<optgroup key={key} label={key.toLocaleUpperCase()}>
{[...value].sort(sortByDateFromLogs).map((item) => (
<option key={item} value={item}>
{item}
</option>
))}
</optgroup>
);
})}
</>
);
}
function TodayOptions() {
const todayFiles = Object.values(groups)
.map((group) => {
const today = new Date().toLocaleDateString().replace(/\//g, '-');
return group.filter((file) => file.includes(today));
})
.flat();
if (todayFiles.length === 0) {
return null;
}
return (
<optgroup label="Today">
{todayFiles.map((item) => (
<option key={item}>{item}</option>
))}
</optgroup>
);
}
}
原测:
it('should change option', () => {
render(
<SelectFile
fileList={fileList}
handleChange={handleChange}
selected=""
/>,
);
const selectElement = screen.getByDisplayValue('Select an option');
const allOptions = screen.getAllByRole('option');
const optionSelected = fileList.adonis[1];
expect(selectElement).toHaveValue('');
act(() => {
userEvent.selectOptions(selectElement, optionSelected);
});
expect(handleChange).toHaveBeenCalledTimes(1);
expect(selectElement).toHaveValue(optionSelected); // returns "" (default value)
expect((allOptions[0] as HTMLOptionElement).selected).toBe(false);
expect((allOptions[1] as HTMLOptionElement).selected).toBe(true);
expect((allOptions[2] as HTMLOptionElement).selected).toBe(false);
expect((allOptions[3] as HTMLOptionElement).selected).toBe(false);
expect((allOptions[4] as HTMLOptionElement).selected).toBe(false);
});
以及使用渲染后的修改后的测试 html:
it('should change option', () => {
render(
<div>
<div className="flex mr-10">
<h3 className="text-lg font-bold mr-4">Select a file</h3>
<select className="bg-slate-600 rounded w-auto">
<option value="">Select an option</option>
<optgroup label="ADONIS">
<option value="adonis-03-02-2022.json">
adonis-03-02-2022.json
</option>
<option value="adonis-02-02-2022.json">
adonis-02-02-2022.json
</option>
</optgroup>
<optgroup label="ERRORS">
<option value="http_errors-03-03-2022.log">
http_errors-03-03-2022.log
</option>
<option value="http_errors-04-02-2022.log">
http_errors-04-02-2022.log
</option>
</optgroup>
</select>
</div>
</div>,
);
const selectElement = screen.getByDisplayValue('Select an option');
const allOptions = screen.getAllByRole('option');
const optionSelected = fileList.adonis[1];
expect(selectElement).toHaveValue('');
act(() => {
userEvent.selectOptions(selectElement, optionSelected);
});
expect(selectElement).toHaveValue(optionSelected); // this returns the optionSelected value
expect((allOptions[0] as HTMLOptionElement).selected).toBe(false);
expect((allOptions[1] as HTMLOptionElement).selected).toBe(true);
expect((allOptions[2] as HTMLOptionElement).selected).toBe(false);
expect((allOptions[3] as HTMLOptionElement).selected).toBe(false);
expect((allOptions[4] as HTMLOptionElement).selected).toBe(false);
});
考虑到它适用于修改后的测试,我无法解释为什么它不适用于原始测试。
我认为这是由于 optgroup,但似乎并非如此,所以现在我不知道为什么。
编辑:测试最终版:
it('should change option', () => {
const mockHandleChange = handleChange.mockImplementation(
(cb) => (e) => cb(e.target.value),
);
render(
<SelectWrapper fileList={fileList} handleChange={mockHandleChange} />,
);
const selectElement = screen.getByDisplayValue('Select an option');
const optionSelected = fileList.adonis[1];
expect(selectElement).toHaveValue('');
act(() => {
userEvent.selectOptions(selectElement, optionSelected);
});
expect(handleChange).toHaveBeenCalledTimes(2); // 1 for cb wrapper, 1 for select
expect(selectElement).toHaveValue(optionSelected);
});
});
const SelectWrapper = ({ handleChange, fileList }) => {
const [selected, setSelected] = useState('');
const mockHandleChange = handleChange(setSelected);
return (
<SelectFile
fileList={fileList}
handleChange={mockHandleChange}
selected={selected}
/>
);
};
我创建了一个包装器,使它像您在另一个组件中使用的那样,包装了模拟函数,现在它更改了值,您可以访问模拟。
因为在您的测试中您只呈现 Select
(这是一个受控组件:它从其父级接收当前值和 onChange
回调),具有固定的 selected
属性,当你触发 select 上的更改事件时,你不能指望 selected 选项会更改。您只能期望 onChange
回调已被调用(就像您所做的那样)。
对于这种组件,您需要测试是否尊重 selected
道具(selected 选项是好的),并且当用户调用时调用提供的回调选择一个新选项(你已经完成了这部分)。
您需要使用现有选项添加测试作为 selected
道具(不是空字符串),然后检查 selected
选项是否正确。我建议你使用 https://github.com/testing-library/jest-dom#tohavevalue from https://github.com/testing-library/jest-dom.
基本上,我正在尝试测试具有 select.
的组件尝试测试组件时,测试失败,返回的是默认值而不是更改后的值。
但是当我使用渲染组件的 HTML 时(来自 screen.debug())它起作用了。
组件:
export function SelectFile({
fileList,
handleChange,
selected,
}) {
return (
<select
className="bg-slate-600 rounded w-auto"
onChange={onChange}
value={selected}
>
<option value="">Select an option</option>
<TodayOptions />
<AllOptions />
</select>
);
function AllOptions() {
return (
<>
{Object.entries(groups).map(([key, value]) => {
return (
<optgroup key={key} label={key.toLocaleUpperCase()}>
{[...value].sort(sortByDateFromLogs).map((item) => (
<option key={item} value={item}>
{item}
</option>
))}
</optgroup>
);
})}
</>
);
}
function TodayOptions() {
const todayFiles = Object.values(groups)
.map((group) => {
const today = new Date().toLocaleDateString().replace(/\//g, '-');
return group.filter((file) => file.includes(today));
})
.flat();
if (todayFiles.length === 0) {
return null;
}
return (
<optgroup label="Today">
{todayFiles.map((item) => (
<option key={item}>{item}</option>
))}
</optgroup>
);
}
}
原测:
it('should change option', () => {
render(
<SelectFile
fileList={fileList}
handleChange={handleChange}
selected=""
/>,
);
const selectElement = screen.getByDisplayValue('Select an option');
const allOptions = screen.getAllByRole('option');
const optionSelected = fileList.adonis[1];
expect(selectElement).toHaveValue('');
act(() => {
userEvent.selectOptions(selectElement, optionSelected);
});
expect(handleChange).toHaveBeenCalledTimes(1);
expect(selectElement).toHaveValue(optionSelected); // returns "" (default value)
expect((allOptions[0] as HTMLOptionElement).selected).toBe(false);
expect((allOptions[1] as HTMLOptionElement).selected).toBe(true);
expect((allOptions[2] as HTMLOptionElement).selected).toBe(false);
expect((allOptions[3] as HTMLOptionElement).selected).toBe(false);
expect((allOptions[4] as HTMLOptionElement).selected).toBe(false);
});
以及使用渲染后的修改后的测试 html:
it('should change option', () => {
render(
<div>
<div className="flex mr-10">
<h3 className="text-lg font-bold mr-4">Select a file</h3>
<select className="bg-slate-600 rounded w-auto">
<option value="">Select an option</option>
<optgroup label="ADONIS">
<option value="adonis-03-02-2022.json">
adonis-03-02-2022.json
</option>
<option value="adonis-02-02-2022.json">
adonis-02-02-2022.json
</option>
</optgroup>
<optgroup label="ERRORS">
<option value="http_errors-03-03-2022.log">
http_errors-03-03-2022.log
</option>
<option value="http_errors-04-02-2022.log">
http_errors-04-02-2022.log
</option>
</optgroup>
</select>
</div>
</div>,
);
const selectElement = screen.getByDisplayValue('Select an option');
const allOptions = screen.getAllByRole('option');
const optionSelected = fileList.adonis[1];
expect(selectElement).toHaveValue('');
act(() => {
userEvent.selectOptions(selectElement, optionSelected);
});
expect(selectElement).toHaveValue(optionSelected); // this returns the optionSelected value
expect((allOptions[0] as HTMLOptionElement).selected).toBe(false);
expect((allOptions[1] as HTMLOptionElement).selected).toBe(true);
expect((allOptions[2] as HTMLOptionElement).selected).toBe(false);
expect((allOptions[3] as HTMLOptionElement).selected).toBe(false);
expect((allOptions[4] as HTMLOptionElement).selected).toBe(false);
});
考虑到它适用于修改后的测试,我无法解释为什么它不适用于原始测试。 我认为这是由于 optgroup,但似乎并非如此,所以现在我不知道为什么。
编辑:测试最终版:
it('should change option', () => {
const mockHandleChange = handleChange.mockImplementation(
(cb) => (e) => cb(e.target.value),
);
render(
<SelectWrapper fileList={fileList} handleChange={mockHandleChange} />,
);
const selectElement = screen.getByDisplayValue('Select an option');
const optionSelected = fileList.adonis[1];
expect(selectElement).toHaveValue('');
act(() => {
userEvent.selectOptions(selectElement, optionSelected);
});
expect(handleChange).toHaveBeenCalledTimes(2); // 1 for cb wrapper, 1 for select
expect(selectElement).toHaveValue(optionSelected);
});
});
const SelectWrapper = ({ handleChange, fileList }) => {
const [selected, setSelected] = useState('');
const mockHandleChange = handleChange(setSelected);
return (
<SelectFile
fileList={fileList}
handleChange={mockHandleChange}
selected={selected}
/>
);
};
我创建了一个包装器,使它像您在另一个组件中使用的那样,包装了模拟函数,现在它更改了值,您可以访问模拟。
因为在您的测试中您只呈现 Select
(这是一个受控组件:它从其父级接收当前值和 onChange
回调),具有固定的 selected
属性,当你触发 select 上的更改事件时,你不能指望 selected 选项会更改。您只能期望 onChange
回调已被调用(就像您所做的那样)。
对于这种组件,您需要测试是否尊重 selected
道具(selected 选项是好的),并且当用户调用时调用提供的回调选择一个新选项(你已经完成了这部分)。
您需要使用现有选项添加测试作为 selected
道具(不是空字符串),然后检查 selected
选项是否正确。我建议你使用 https://github.com/testing-library/jest-dom#tohavevalue from https://github.com/testing-library/jest-dom.