hook 中比较难搞的应该算是 useEffect ,我花了很长时间来看别人是如何对它进行单元测试的,但是并没有得到一些有用的信息,后来我仔细想了想,其实这个问题应该这样来想, useEffect 是用来封装副作用的,它只用来负责副作用的运行时机,对于副作用干了什么,对于 useEffect 完全是透明的。因此我们没有必要对它进行单元测试,而应该在副作用的实现层确保它的正确性。但我们通常会将副作用的实现与 hook 的实现耦合起来,那怎么对副作用的实现进行测试呢?这里可以分两种情况。
useEffect 会运行 props 中传递的回调函数
这种情况相对简单一些,只需要通过 jest.fn() 来构造一个 spy 函数,之后通过上一节的方式渲染 hook ,通过 jest 对于 spy 函数的 api 来进行验证即可。
useEffect 自成一体
这种情况下,我当前是通过将副作用代码,直接声明在 hook 外部的方式来进行测试的,比如:
export function updateDocumentTitle(title) { document.title = title return () => { document.title = 'default title' } } export function useDocumentTitle(title) { useEffect(() => updateDocumentTitle(title), [title]) }
这样,只需要单独测试 updateDocumentTitle 就好,而不需要在 useEffect 上花费功夫了。
这里可能有的人会问,你这里无法覆盖 title 改变时, effect 是否重新运行的场景,确实,当前我也没有办法解决这种问题,如果要解决,办法还是有的,就是通过 useDocumentTitle 的参数,来传递 updateDocumentTitle ,但这对于代码有很强的侵入性,我不建议这样做,如果 hook 本身的实现方式就是这样,那完全可以针对它编写相关的测试用例,如果不是,也没有必要为了写测试用例而改写原来的实现。
hook 无法被测试的原因
在对公司项目各个 hook 编写单元测试时,发现一些 hook 非常难以测试,大体的特征如下:
hook 的实现非常复杂,状态繁多,依赖繁多
hook 的实现不复杂,但外部依赖难以 mock
hook 的实现自成一体,没有入口
关于第一点,解决的方法当然是,化繁为简,将复杂的 hook,划分为多个简单的 hook,使其职责更单一。对于第二点,如果外部依赖难以 mock ,我建议将它的测试用例放到集成测试阶段进行实现,而不要花费过多精力在编写单元测试的 mock 逻辑上。最后一点的解决方法详见上一小节。