简体   繁体   English

如何在笑话和酶中为 useState Hook 设置初始状态值?

[英]How to set initial state value for useState Hook in jest and enzyme?

I know this question is already asked here: How to set initial state for useState Hook in jest and enzyme?我知道这里已经有人问过这个问题: How to set initial state for useState Hook in jest andase?

const [state, setState] = useState([]);

And I totally agree with Jimmy's Answer to mock the useState function from test file but I have some extended version of this question, "What if I have multiple useState statements into the hooks, How can I test them and assign the respective custom values to them?"我完全同意Jimmy's Answer从测试文件中模拟 useState 函数,但我有这个问题的一些扩展版本,“如果我有多个 useState 语句进入钩子,我如何测试它们并为它们分配相应的自定义值?”

I have some JSX rendering with the condition of hook's state values and depending on the values of that state the JSX is rendering.我有一些带有钩子状态值条件的 JSX 渲染,并且取决于JSX正在渲染的状态值。

How Can I test those JSX by getting them into the wrapper of my test case code?如何通过将这些 JSX 放入我的测试用例代码的wrapper来测试它们?

Upon the answer you linked, you can return different values for each call of a mock function:根据您链接的答案,您可以为模拟函数的每次调用返回不同的值:

let myMock = jest.fn();

myMock
  .mockReturnValueOnce(10)
  .mockReturnValueOnce('x')
  .mockReturnValue(true);

In my opinion this is still brittle.在我看来,这仍然很脆弱。 You may modify the component and add another state later, and you would get confusing results.您可以稍后修改组件并添加另一个状态,并且您会得到令人困惑的结果。

Another way to test a React component is to test it like a user would by clicking things and setting values on inputs.测试 React 组件的另一种方法是像用户一样通过单击事物并在输入上设置值来测试它。 This would fire the event handlers of the component, and React would update the state just as in real configuration.这将触发组件的事件处理程序,而 React 将像在实际配置中一样更新状态。 You may not be able to do shallow rendering though, or you may need to mock the child components .您可能无法进行浅层渲染,或者您可能需要 模拟子组件

If you prefer shallow rendering, maybe read initial state values from props like this:如果您更喜欢浅层渲染,可以像这样从 props 读取初始状态值:

function FooComponent({initialStateValue}) {
  const [state, setState] = useState(initialStateValue ?? []);
}

If you don't really need to test state but the effects state has on children, you should (as some comments mention) just then test the side effect and treat the state as an opaque implementation detail.如果您真的不需要测试状态但是状态对孩子的影响,您应该(如某些评论所述)然后测试副作用并将状态视为不透明的实现细节。 This will work if your are not using shallow rendering or not doing some kind of async initialization like fetching data in an effect or something otherwise it could require more work.如果您不使用浅层渲染或不进行某种异步初始化(例如在效果中获取数据或其他可能需要更多工作),这将起作用。

If you cant do the above, you might consider removing state completely out of the component and make it completely functional.如果您不能执行上述操作,您可以考虑将状态完全从组件中移除并使其完全正常运行。 That way any state you need, you can just inject it into the component.这样你需要的任何状态,你都可以将它注入到组件中。 You could wrap all of the hooks into a 'controller' type hook that encapsulates the behavior of a component or domain.您可以将所有钩子包装到一个“控制器”类型的钩子中,该钩子封装了组件或域的行为。 Here is an example of how you might do that with a <Todos /> .下面是一个示例,说明如何使用<Todos />做到这一点。 This code is 0% tested, its just written to show a concept.这段代码是 0% 测试的,它只是为了展示一个概念。

const useTodos = (state = {}) => {
  const [todos, setTodos] = useState(state.todos);
  const id = useRef(Date.now());
  
  const addTodo = useCallback((task) => {
    setTodos((current) => [...current, { id: id.current++, completed: false, task }]);
  }, []);

  const removeTodo = useCallback((id) => {
    setTodos((current) => current.filter((t) => t.id !== id));
  }, []);
  
  const completeTodo = useCallback((id) => {
    setTodos((current) => current.map((t) => {
      let next = t;
      
        if (t.id === id) {
        next = { ...t, completed: true };
      }
      
      return next;
    }))
  }, []);

  return { todos, addTodo, removeTodo, completeTodo };
};


const Todos = (props) => {
  const { todos, onAdd, onRemove, onComplete } = props;
  
  const onSubmit = (e) => {
    e.preventDefault();
    onAdd({ task: e.currentTarget.elements['todo'].value });
  }
  
  return (
    <div classname="todos">
      <ul className="todos-list">
        {todos.map((todo) => (
          <Todo key={todo.id} onRemove={onRemove} onComplete={onComplete} />
        )}
      </ul>
      <form className="todos-form" onSubmit={onSubmit}>
        <input name="todo" />
        <button>Add</button>
      </form>
    </div>
  );
};

So now the parent component injects <Todos /> with todos and callbacks.所以现在父组件向<Todos />注入了待办事项和回调。 This is useful for testing, SSR, ect.这对于测试、SSR 等很有用。 Your component can just take a list of todos and some handlers, and more importantly you can trivially test both.你的组件可以只需要一个待办事项列表和一些处理程序,更重要的是你可以简单地测试两者。 For <Todos /> you can pass mocks and known state and for useTodos you would just call the methods and make sure the state reflects what is expected.对于<Todos />您可以传递useTodos和已知状态,对于useTodos您只需调用方法并确保状态反映预期的内容。

You might be thinking This moves the problem up a level and it does, but you can now test the component/logic and increase test coverage.您可能会想,这将问题提升了一个级别,确实如此,但您现在可以测试组件/逻辑并增加测试覆盖率。 The glue layer would require minimal if any testing around this component, unless you really wanted to make sure props are passed into the component.如果围绕这个组件进行任何测试,胶水层将需要最少的测试,除非你真的想确保 props 被传递到组件中。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM