Effect 测试里怎么替换环境变量配置?

测试想换一组配置,最朴素的做法是改全局环境变量,并且要记得还原。漏掉一次还原,污染的就是后面所有的测试。

答案是:替换 ConfigProvider。 Config 从当前的 ConfigProvider 读取,默认的提供者读环境变量,你可以用 ConfigProvider.fromEnvRecord 造一个只含测试值的提供者,再通过 Layer 提供进去,业务代码一行不改。

原因是提供者就是一个普通值,和任何其他 Layer 一样可以替换。下面给出具体写法,以及另一种更轻的做法。

怎么给 Config 换一个配置来源?

假设配置按服务的方式提供:Settings 是用 Context.Service 声明的服务,Settings.layer 在构造时读取 AppConfig。这种写法的来龙去脉见 Effect 里怎么读配置,而不是直接读 process.env?。

import { Config, Context, Layer } from "effect"

const AppConfig = Config.all({
  port: Config.Int("PORT").pipe(Config.withDefault(3000)),
  apiKey: Config.Redacted("API_KEY"),
  db: Config.all({ url: Config.String("URL") }).pipe(Config.nested("DB"))
})

class Settings extends Context.Service<Settings, Config.Success<typeof AppConfig>>()("app/Settings") {
  static readonly layer = Layer.effect(Settings, AppConfig)
}

测试里用一个假的环境记录:

import { ConfigProvider } from "effect"

const fakeEnv = ConfigProvider.fromEnvRecord({
  API_KEY: "test-key",
  DB_URL: "pg://localhost/test"
})

const testLayer = Settings.layer.pipe(
  Layer.provide(ConfigProvider.layer(fakeEnv))
)

Config.nested("DB") 会给键加前缀,所以 DB_URL 对应配置里的 url。PORT 没写,会落到 withDefault(3000)。

这样做比改全局环境变量好在哪?

不需要改全局环境变量,也不会污染其他测试。开发、测试、生产的差别,本质是「配置从哪读」的差别,换提供者就是换来源,业务代码对此一无所知。

能不能完全绕过 Config?

可以:在测试里直接 Layer.succeed(Settings, { ... }) 提供一个写死的配置。

两种都可以。换提供者的做法连「配置读取逻辑本身」一起测,写死的做法更轻。我的选择标准是:要验证缺项、格式错这类读取行为就换提供者,只想给业务代码一个配置就直接 Layer.succeed。

版本说明:原稿没有标注 effect 的具体版本号,本文也不给出版本。ConfigProvider.fromEnvRecord 与 ConfigProvider.layer 的写法沿用原稿,没有在这里重新跑过,也没有跑过完整的测试用例;上面「选择标准」是我基于原稿两种做法的归纳。