在dom-testing-library或react-testing-library中测试输入值的最佳方法是什么?

88

dom-testing-library/react-testing-library 中,测试一个元素的值,最好的方法是什么?

我的做法是通过 closest() 方法获取原始的输入元素本身,这样我就可以直接访问 value 属性:

const input = getByLabelText("Some Label")
expect(input.closest("input").value).toEqual("Some Value")

我希望有一种方法可以在不直接访问HTML属性的情况下完成这个任务。这似乎不符合测试库的精神。也许可以使用像jest-dom toHaveTextContent matcher这样的匹配器:

const input = getByLabelText("Some Label")
expect(input).toHaveTextContent("Some Value")

更新

根据评论中的请求,这里有一个代码示例,展示了我在构建应用时遇到需要测试输入框值的情况。

这是我在应用程序中构建的一个模态组件的简化版本。整个想法是,基于一个字符串prop,模态框打开并预填充了一些文本。用户可以自由编辑该输入,并通过按下按钮提交它。但是,如果用户关闭模态框,然后重新打开它,我希望将文本重置为原始的字符串prop。我编写了一个测试,因为模态框的先前版本未能重置输入值。

我正在使用TypeScript编写此内容,以便每个prop的类型非常清晰。

interface Props {
  onClose: () => void
  isOpen: boolean
  initialValue: string
}

export default function MyModal({ onClose, isOpen, initialValue }) {
  const [inputValue, setInputValue] = useState(initialValue)

  // useEffect does the reset!
  useEffect(() => {
    if (!isOpen) {
      setNameInput(initialValue)
    }
  }, [isOpen, initialValue])

  return (
    <SomeExternalLibraryModal isOpen={isOpen} onClose={onClose}>
      <form>
        <input
          value={inputValue}
          onChange={(e: ChangeEvent<HTMLInputElement>) =>
            setInputValue(e.target.value)
          }
        />
        <button onClick={onClose}>Cancel</button>
      </form>
    </SomeExternalLibraryModal>
  )
}

你能提供整个组件吗?RTL方法更注重黑盒测试,所以我猜一个“好”的测试方式是触发使用输入的事件,而不是验证值。然后,你可以模拟需要调用的服务,并验证是否使用了正确的值进行调用。还有很多其他解决方案,所以分享代码,我会贴出一个示例。 - Arnaud Claudel
@ArnaudClaudel 我提供了一个代码示例。我很想知道您如何为该功能编写RTL测试。谢谢 :) - ecbrodie
你什么时候使用 inputValue?我在 value={inputValue} 中看到它,但那是用于输入栏的,当用户点击按钮时你在哪里使用它? - Arnaud Claudel
@ArnaudClaudel 就像我说的,这只是我们建立的实际组件的一个非常极简化版本。 我没有包括任何逻辑,因为它与我的问题无关。 假设 inputValue 用于类似于单击“更新”按钮时表单的 onSubmit 处理程序之类的东西(再次强调,由于与我最初的问题无关,因此被省略)。 - ecbrodie
7个回答

92

你对这个测试库要求你进行的测试方法持怀疑态度是正确的。对于这个问题,最简单的答案是使用 getByDisplayValue查询。它将搜索具有您尝试查找的值的输入、textarea或选择。例如,以您的组件为例,如果我想验证 inputValue = 'test',我会这样搜索:

expect(screen.getByDisplayValue('test')).toBeInTheDocument();

这就是您需要做的全部。我假设您的测试仅呈现 MyModal 组件。即使您有多个输入,从测试哲学的角度来看也无关紧要。只要 getByDisplayValue 找到任何具有该值的输入,测试就成功了。如果您有多个输入,并且想要测试确切的输入是否具有该值,那么您可以深入元素以确定它是正确的输入:

注意: 您需要jest-dom才能使其生效。

expect(screen.getByDisplayValue('test')).toHaveAttribute('id', 'the-id');

或者(不使用 jest-dom):

expect(screen.getByDisplayValue('test').id).toBe('the-id');

你当然可以搜索任何你想要的属性。

检测值的最后一种选择是通过角色查找输入框。除非你添加一个标签并将其与输入框关联,否则这在你的示例情况下不起作用,方法是通过 htmlFor 属性将标签与输入框相关联。然后你可以像这样进行测试:

expect(screen.getByRole('input', { name: 'the-inputs-id' })).toHaveValue('test');

或(不使用 jest-dom):

expect(screen.getByRole('input', { name: 'the-inputs-id' }).value).toBe('test');

我认为这是在保证正确的输入具有价值的最佳测试方法。我建议使用getByRole方法,但同样需要为您的示例添加标签。


有没有办法通过displayValue和label获取? - Noname
同时进行吗?我不这么认为,但我需要知道你想要实现什么。 - Devin Fields
我同意使用 getByRole 的最后一个选项,它确保您以更可访问的方式设置了您的 HTML,确保您正在查看您期望值显示的元素,并允许您使用 aria 角色上的选项进行测试。 - alexK85

19
你可以使用 screen.getByDisplayValue() 方法获取具有显示值的输入元素,并将其与您的元素进行比较。
type TestElement = Document | Element | Window | Node

function hasInputValue(e: TestElement, inputValue: string) {
  return screen.getByDisplayValue(inputValue) === e
}

在您的测试中:

const input = screen.getByLabelText("Some Label")

fireEvent.change(input, { target: { value: '123' } })
expect(hasInputValue(input, "123")).toBe(true)

我曾经在使用userEvent时遇到了一些问题,它忽略了一些字符,但是这个方法完美地解决了我的问题。谢谢! - Bjørn Olav Jalborg
有没有办法通过displayValue和label获取? - Noname

11
< p > < code >expect(screen.getByLabelText("Name")).toHaveValue("hello"); - 这将为您获取输入值 :)


<label class="label" for="name">
      Name
</label>
<div class="control ">
       <input
          class="input"
          for="name"
          id="name"
          name="name"
          value="hello"
       />
</div>

测试:

userEvent.type(screen.getByLabelText("Name"), "hello")
await waitFor(() => {
   expect(screen.getByLabelText("Name")).toHaveValue("hello");
});

8

使用 @testing-library/dom(或任何封装的库

你可以这样做:

expect(inputField).toHaveDisplayValue('some input value');

完整示例:

test('should show input with initial value set', async () => {
  render(<Input type="text" value="John Doe" data-testid="form-field-firstname" />);

  const inputField = await screen.findByTestId(`form-field-firstname`);
  await waitFor(() => expect(inputField).toHaveDisplayValue('John Doe')));
});

你没有说明如何在没有 testId 的情况下访问输入。示例已经很清楚地说明了这一点。 - Alexey Nikonov

1

使用测试库进行测试是非常清晰的方法。

//In describe
  const renderComponent = (searchInputValue, handleSearchInputValue) => {
    const wrapper = render(<yourComponentWithInput
      value={searchInputValue}
      onChange={handleSearchInputValue}
    />);
    return wrapper;
  };


//In test
    const mockHandleSearchInputValue = jest.fn();
    const { getByLabelText } = renderComponent('g', mockHandleSearchInputValue);
    const inputNode = getByLabelText('Search label'); // your  input label
    expect(inputNode.value).toBe('s'); // to test input value
    fireEvent.change(inputNode, { target: { value: 'su' } }); // triggers onChange event
    expect(mockHandleSearchInputValue).toBeCalledWith('su'); // tests if onChange handler is called with proper value


1
Property 'value' does not exist on type 'HTMLElement' - ChumiestBucket

1

screen.getByRole('textbox', {name: '屏幕可读取的名称 / aria-label 值'})

getByRole('textbox'...


0

我检查了所有的答案,但没有人提到我们可以使用querySelector访问输入,步骤如下:

  1. 使用screen渲染和访问UI组件
  2. 使用getByTestIdgetByText或其他方法获取父级HTMLElement
  3. 使用querySelector访问输入
it('Input value should be 1'() => {
  const input = screen.getByTestId('wrapperId').querySelector('input')
  expect(input).toHaveValue(1);
})


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接