Effect 测试里该共享 Layer 吗?

同一个用例单独跑能过,换个顺序跑就挂,这是测试之间共享了状态的典型症状。用 @effect/vitest 共享 Layer 时,这个问题可能被你亲手引进来。

默认让每个测试自己 Effect.provide 一份 Layer;只有构建成本高的资源才用 layer(...) 让整个 describe 块共享,并且接受块内所有用例看到的是同一个实例。 需要隔离的状态,不要共享。

选择的本质是速度和隔离的交换:共享省掉重复构建,代价是服务内部的可变状态会在用例之间传递。Layer 本身是什么,见 Layer 怎么装配依赖;测试里怎么用替身 Layer,见 Effect 代码怎么测试,才不用 mock 整个模块?。

默认应该怎么做?

在每个测试里 Effect.provide 自己的 Layer。这是最隔离的做法,用例之间没有任何共享,代价是 Layer 每次都重新构建。对大多数替身 Layer 来说,这个代价可以忽略。

什么时候用 layer(...) 共享?

构建成本高的资源,比如启动一个测试数据库。layer(...) 让整个 describe 块共享一次构建,结束时统一释放:

import { layer } from "@effect/vitest"

layer(SomeExpensiveLayer)("集成测试", (it) => {
  it.effect("用例", () => Effect.gen(function* () { /* ... */ }))
})

这里的 SomeExpensiveLayer 是占位名,换成你自己的 Layer。

共享会带来什么问题?

块内所有用例拿到的是同一个实例,服务内部的可变状态会在用例之间传递。用例一旦依赖执行顺序,就互相耦合,换个顺序就挂。

所以共享块里的用例,不要依赖别的用例留下的状态,也不要假设执行顺序。需要隔离的状态,放回到各自的 Effect.provide 里。

要不要用真实外部服务当共享资源?

集成测试有价值,但应当是少量、有意为之的一层。业务逻辑的主体测试应当跑在替身上,快而稳定;共享 Layer 的块更适合放那一小层集成测试。

本文代码与结论来自我整理的一份 Effect 教程章节,这次改写没有重新运行,因此不能保证在你的版本上一定通过。layer(...) 的行为描述(共享一次构建、结束时统一释放)以原章节为准,我没有自己验证。