MobX 应用应该怎么测试 Store 和组件?
测试 MobX 应用时,重点不是验证“observable 是否真的能响应”,而是验证业务状态在 action、computed、reaction 和组件渲染之间有没有按预期流动。优先测试 Store,因为大部分规则都在那里;组件测试只覆盖用户能看到和能操作的行为。API 边界可以用 mock 或 MSW 隔离,reaction 这类副作用要记得释放 disposer。不要把每个 observable 字段都测一遍,那会让测试和实现细节绑死,重构一次就碎一片。
一个比较稳的分层是:Store 单元测试覆盖状态变化和异常分支,组件测试覆盖点击、输入、展示,少量集成测试覆盖多个 store 的协作。MobX 的响应式机制本身已经由框架保证,业务测试没必要重复证明它存在。真正容易出问题的是异步 action 的 loading 恢复、错误消息、reaction 泄漏、组件没被 observer 包裹,以及 mock 数据和真实接口结构不一致。
tsclass UserStore { user = null; loading = false; error = ''; constructor(private api) { makeAutoObservable(this); } get isLoggedIn() { return Boolean(this.user); } async login(payload) { this.loading = true; try { this.user = await this.api.login(payload); } catch (e) { this.error = e.message; throw e; } finally { this.loading = false; } } }
追问
Store 测试应该测状态字段还是业务行为?
优先测业务行为,也就是调用 action 之后,对外可观察的状态和 computed 结果是否正确。直接断言每个内部字段会让测试过度依赖实现,比如后来把两个字段合并成一个对象,功能没坏但测试全红。边界是错误状态、loading、权限标记这类会直接影响 UI 的字段,仍然应该明确断言。写法上可以把 store 当成普通类实例,不需要为了测试专门启动 React。取舍是测试会少覆盖一些内部细节,但更能支持后续重构。
MobX 的异步 action 有哪些测试坑?
最大的坑是没有等待异步结束就断言,导致测试偶发通过或失败。await store.login() 后再检查 user、loading、error,比用固定 setTimeout 稳得多。另一个坑是错误分支只测抛错,不测状态恢复,结果线上 loading 一直转。异步 action 里如果用了 runInAction,测试仍然关注最终行为,不必断言 MobX 的内部调度顺序。边界是包含防抖、轮询或取消请求的 action,需要同时控制 timer 和请求 mock,否则失败会很随机。
组件测试里要不要 mock 整个 Store?
如果目标是验证组件交互,mock 一个最小 Store 很有用,速度快,也能把失败范围限制在组件。若要验证多个 store 协作、路由跳转或真实 reaction,就应该使用接近真实的 store 实例,甚至配合 MSW mock 网络。过度 mock 的问题是组件看起来测过了,但和真实 store 的 observable 更新方式不一致。一个实用取舍是:单组件用轻量 mock,关键业务流用真实 store 加 mock API。还要注意组件必须在测试里保持和生产一致的 Provider 结构,否则通过的测试可能只是绕开了依赖注入问题。
reaction、autorun 这类副作用怎么测?
先触发会改变依赖的 action,再断言副作用是否发生,例如调用埋点、写 localStorage 或更新另一个 store。测试结束时一定要执行 disposer,否则后续用例可能被上一个 reaction 影响。这里的边界是不要测试 MobX 会不会追踪依赖,应该测试你注册的副作用是否符合业务预期。若副作用包含防抖或定时器,需要配合 fake timers,并在用例后恢复真实 timer。踩坑点是 reaction 默认可能立即执行,也可能等依赖变化才执行,测试前要明确当前配置。
spy、trace、isObservable 适合放进断言吗?
spy 和 trace 更适合排查响应链路,不建议长期作为核心断言,因为它们会让测试贴着 MobX 实现细节跑。isObservable 可以在库代码或 store 工厂里做少量保护,但业务测试里通常没必要。真正有价值的断言是用户状态、组件输出和副作用结果。踩坑点是为了提高“测试覆盖率”去检查 observable 类型,最后覆盖率上去了,坏业务却没被测到。取舍上,调试工具可以临时打开,但最终测试最好回到业务可见结果。
写段测试
tsit('logs in and resets loading', async () => { const api = { login: jest.fn().mockResolvedValue({ name: 'Ada' }) }; const store = new UserStore(api); await store.login({ username: 'ada' }); expect(api.login).toHaveBeenCalledWith({ username: 'ada' }); expect(store.user.name).toBe('Ada'); expect(store.isLoggedIn).toBe(true); expect(store.loading).toBe(false); });
MobX 测试的主线很简单:Store 测规则,组件测行为,reaction 测副作用,API 边界用可控替身。只要少测实现细节,多测状态变化带来的可见结果,测试既能挡住回归,也不会在重构时变成负担。遇到测试不稳定时,先检查异步等待、disposer、timer 和 mock 清理,通常比怀疑 MobX 本身更接近答案。