如何使用 Jest 模拟异步函数

Posted

技术标签:

【中文标题】如何使用 Jest 模拟异步函数【英文标题】:How to mock an asynchronous function using Jest 【发布时间】:2021-08-11 21:49:31 【问题描述】:

我一直在开玩笑地模拟我的提交功能,但我总是收到这个错误:

expect(jest.fn()).toHaveBeenCalled()

Expected number of calls: >= 1
Received number of calls:    0

我该如何解决这种错误?

这是我的测试文件。

it('checks the function submitTodo', () => 
    const submitTodo = jest.fn();
    const  getByTestId  = render(<InputField />);
    const input = getByTestId('input');
    fireEvent.change(input,  target:  value: 'test'  )

    fireEvent.submit(getByTestId('form'))
    expect(submitTodo).toHaveBeenCalled()
)

这是我调用异步函数的功能组件。

import  useState  from 'react';
import  firestore  from 'firebase/firebaseConfig';
import firebase from 'firebase/firebaseConfig';
import styles from 'theme/main.module.scss';

const InputField = () => 
  const [todo, setTodo] = useState('');

  const submitTodo = async (e) => 
    e.preventDefault();
    try 
      await firestore.collection('todo').add(
        todo,
        timestamp: firebase.firestore.FieldValue.serverTimestamp(),
      );
     catch (error) 
      alert(error);
    
    setTodo('');
  ;
  return (
    <form
      data-testid='form'
      className=styles.inputFieldContainer
      onSubmit=(e) => submitTodo(e)
    >
      <input
        data-testid='input'
        className=styles.inputFieldContainer__input
        placeholder='Please enter a todo.'
        required
        value=todo
        onChange=(e) => setTodo(e.target.value)
      />
      <button
        data-testid='submitBtn'
        className=styles.inputFieldContainer__btn
        type='submit'
      >
        Submit
      </button>
    </form>
  );
;

export default InputField;

【问题讨论】:

【参考方案1】:

submitTodoInputField 的内部函数。你不能直接使用 jest.fn() 来模拟它。如果你只是想测试 submitTodo 函数是否被调用,那么你可以这样做。

it('checks the function submitTodo', () => 
    const submitTodoForTest = jest.fn();
    const  getByTestId  = render(<InputField submitTodoForTest=submitTodoForTest />);
    const input = getByTestId('input');
    fireEvent.change(input,  target:  value: 'test'  )

    fireEvent.submit(getByTestId('form'))
    expect(submitTodoForTest).toHaveBeenCalled()
)

// In your component
  const InputField = (submitTodoForTest = null) => 
  const [todo, setTodo] = useState('');

  const submitTodo = async (e) => 
  submitTodoForTest && submitTodoForTest();
    e.preventDefault();
    try 
      await firestore.collection('todo').add(
        todo,
        timestamp: firebase.firestore.FieldValue.serverTimestamp(),
      );
     catch (error) 
      alert(error);
    
    setTodo('');
  ;
  return (
    ....
  );
;

但是如果你需要测试fireStore是否被调用,那么你也需要模拟fireStore。

【讨论】:

我可以知道你为什么将道具传递给 InputField 组件吗? 这是因为我们不能直接模拟另一个函数内部的函数。 submitTodo 是 InputField 中的一个函数。所以我们在这里传递一个附加道具仅用于测试目的。再次反应测试库不推荐这个。因为我们现在要做的是检查 submitTodo 是否被调用。测试应该更倾向于用户将如何使用我们的应用程序。看看这个 。 testing-library.com/docs/guiding-principlestesting-library.com/docs/… 这是否意味着我应该创建另一个包含 submitFunction 的文件? 不需要,你应该避免断言是否调用了xyz函数。因为用户不在乎你是否在调用某个函数。您需要做的就是断言应用程序的行为方式以及用户在进行交互之前和之后看到的内容。例如,在提交新的 todo 之前断言有多少个 todo -> 现在提交一个新的 todo -> 现在断言新添加的 todo 是否存在于文档中。

以上是关于如何使用 Jest 模拟异步函数的主要内容,如果未能解决你的问题,请参考以下文章

Jest / Enzyme - 生命周期方法中的模拟异步函数

如何使用 jest.fn() 在 jest 中使用 typescript 模拟函数

使用 Jest,我如何检查模拟函数的参数是不是是函数?

如何使用 jest 模拟链式函数调用?

如何使用 Jest 模拟对象中的特定函数?

如何使用 Jest 模拟 JavaScript 窗口对象?