You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React/Jest场景下如何模拟用户篡改HTML option值编写TDD单元测试

问题原因

你写的测试没有按预期失败,首先是测试本身存在几处语法/逻辑错误:

  • 提交按钮的选择器写错:input[type=select] 是无效选择器,你的提交按钮type属性是submit
  • 代码中elements.unitSelect是未定义变量,你已经提前查询到了select元素,直接用它访问options即可
  • 断言逻辑错误:jest.fn()的调用记录是累计的,toHaveBeenCalledWith('fr') 只会判断历史有没有过调用传fr的记录,你需要判断最后一次提交的参数是不是fr,要用toHaveBeenLastCalledWith,最好提前清空mock的调用历史避免干扰。

修正后的测试代码

it('keeps previous countryId if the selected one has been tampered with', () => {
    const onSubmit = jest.fn(() => Promise.resolve())

    const countries = [
        { id: 'fr', label: 'France' },
        { id: 'en', label: 'England' },
    ]

    const { container } = render(
        <CreateTripForm countries={countries} onSubmit={onSubmit} />
    )

    const select = container.querySelector('select[name=countryId]')
    const submitButton = container.querySelector('input[type=submit]')

    // 先选择合法值fr,提交成功
    fireEvent.change(select, { target: { value: 'fr' } })
    fireEvent.click(submitButton)
    expect(onSubmit).toHaveBeenLastCalledWith('fr')
    // 重置mock调用记录,避免历史调用干扰后续判断
    onSubmit.mockClear()

    // 模拟用户篡改option值
    select.options[2].value = 'asgard'
    // 触发change事件,选中被篡改的值
    fireEvent.change(select, { target: { value: 'asgard' } })

    // 提交,预期还是用之前的合法值fr
    fireEvent.click(submitButton)
    expect(onSubmit).toHaveBeenLastCalledWith('fr')
})

这时候运行测试会按预期失败,因为现在的组件逻辑会直接把asgard存到countryId里,提交的时候会传asgard。

组件修复逻辑

你只需要在更新state前加一层白名单校验,判断传入的值是不是在合法的国家id列表里即可:

export const CreateTripForm = ({ countries, onSubmit }) => {
    const [countryId, setCountryId] = useState()
    // 提前生成合法id的白名单集合
    const validCountryIds = new Set(countries.map(c => c.id))

    const submit = async (e) => {
        e.preventDefault()
        if (countryId === undefined) return 

        await onSubmit(countryId)
    }

    return (
        <form onSubmit={submit}>
            <legend>Choose a country</legend>
            
            <label htmlFor="countryId">Country</label>
            <select 
                name="countryId" 
                required 
                value={countryId} 
                onChange={(e) => {
                    const newValue = e.target.value
                    // 只有合法值才更新state
                    if (validCountryIds.has(newValue)) {
                        setCountryId(newValue)
                    }
                }}
            >
                <option value=""></option>
                {countries.map((country) =>
                    <option key={country.id} value={country.id}>{country.label}</option>
                )}
            </select>

            <input type="submit" value="Create a trip" />
        </form>
    )
}

修改后再运行测试,就可以正常通过了。

内容的提问来源于stack exchange,提问作者sonicolasj

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.04 21:27:05