首页 > 解决方案 > React/Jest:模拟用户篡改 HTML

问题描述

在 React vanilla 表单中,我需要解决用户手动编辑选项值以使表单提交错误值的问题。

为了重现,用户检查元素,并手动将valuean更改option为无效值(按域)。

修复很简单,但我想在修复之前以 TDD 方式创建一个失败的单元测试,并且无法弄清楚如何在测试中对此进行建模。我使用 jest 和 react-testing-library。

这是表单代码:

export const CreateTripForm = ({ countries, onSubmit }) => {
    const [countryId, setCountryId] = useState()

    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) => setCountryId(e.target.value)}>
                <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>
    )
}

这是我尝试做的,但测试通过而不是失败:

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=select]')

    // Select the 'fr' option, it works.
    fireEvent.change(select, { target: { value: 'fr' } })

    submitButton.click()
    expect(onSubmit).toHaveBeenCalledWith('fr')

    // Edit an option to have an incorrect value, it should keep the previous value.
    elements.unitSelect.options[2].value = 'asgard'
    fireEvent.change(select, { target: { value: 'asgard' } })

    submitButton.click()
    expect(onSubmit).toHaveBeenCalledWith('fr')
})

标签: reactjsunit-testingjestjsreact-testing-library

解决方案


我以前也有过expect()这样的不好的经历。
您是否尝试过将测试分开?一个代表成功,一个代表失败?
据我所知,第一个expect()并没有重置他已经被称为的东西,所以我想这就是为什么你的测试通过了第二个expect()
试试这样:

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=select]')

    // Select the 'fr' option, it works.
    fireEvent.change(select, { target: { value: 'fr' } })

    submitButton.click()
    expect(onSubmit).toHaveBeenCalledWith('fr')
})

it('should prevent sending with tampered select', () => {
    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=select]')
    elements.unitSelect.options[2].value = 'asgard'
    fireEvent.change(select, { target: { value: 'asgard' } })

    submitButton.click()
    expect(onSubmit).not.toHaveBeenCalled()
})

推荐阅读