Testing configuration with ConfigProvider

In the live version of the app BASE_URL is retrieved from process.env, which is the default configuration provider in effect.

package.json
{
  "name": "effect-getting-started-course",
  "version": "1.0.0",
  "main": "src/index.js",
  "author": "Sandro Maglione",
  "license": "MIT",
  "scripts": {
    "dev": "BASE_URL=https://pokeapi.co tsx src/index.ts",
    "tsc": "tsc",
    "test": "vitest"
  },
  "devDependencies": {
    "@types/node": "^20.14.10",
    "msw": "^2.3.1",
    "tsx": "^4.16.2",
    "typescript": "^5.5.3",
    "vitest": "^2.0.2"
  },
  "dependencies": {
    "@effect/platform": "^0.69.2",
    "effect": "^3.10.0"
  }
}

For testing instead we want to define a new provider that returns http://localhost:3000 instead of https://pokeapi.co.

All requests to http://localhost:3000 will be intercepted by msw that will return the mock response.

ConfigProvider: provider for configuration

Config is a service that retrieves primitive values based on a key:

export class PokeApiUrl extends Context.Tag("PokeApiUrl")<
  PokeApiUrl,
  string
>() {
  static readonly Live = Layer.effect(
    this,
    Effect.gen(function* () {
      // 👇 Get value with `BASE_URL` key using `Config`
      const baseUrl = yield* Config.string("BASE_URL");
      return `${baseUrl}/api/v2/pokemon`;
    })
  );
}

The source of the values passed to Config is defined with ConfigProvider.

For testing we usually define a new ConfigProvider that provides static values instead of accessing process.env.

We do this by using ConfigProvider.fromMap:

import { ConfigProvider } from "effect";

const TestConfigProvider = ConfigProvider.fromMap(
  new Map([["BASE_URL", "http://localhost:3000"]])
);

ConfigProvider.fromMap takes a Map of key-value pairs and returns a new ConfigProvider that provides the values defined in the map.

There are other ways to define a ConfigProvider, you can check the API reference to learn about them all.

Providing ConfigProvider as a layer

Since ConfigProvider is a service, we can wrap it in a layer to provide it to our program.

We use Layer.setConfigProvider to create a layer from a ConfigProvider:

import { ConfigProvider, Layer } from "effect";

const TestConfigProvider = ConfigProvider.fromMap(
  new Map([["BASE_URL", "http://localhost:3000"]])
);

const ConfigProviderLayer = Layer.setConfigProvider(TestConfigProvider);

With this we have ConfigProviderLayer that we can compose as a normal layer. We are going to provide this to our program next.