Skip to content

Instantly share code, notes, and snippets.

@jpb06
Last active December 14, 2024 14:11
Show Gist options
  • Select an option

  • Save jpb06/91d95922314c0af24d1dd38fb0aab5f3 to your computer and use it in GitHub Desktop.

Select an option

Save jpb06/91d95922314c0af24d1dd38fb0aab5f3 to your computer and use it in GitHub Desktop.
Effect - Define layers, consume them and test code depending on them
import { Context, Effect, Layer, pipe } from 'effect';
// ----------------------------------------------
// define layer
class Logger extends Context.Tag('Logger')<
Logger,
{
readonly info: (message: string) => Effect.Effect<void>;
readonly error: (message: string) => Effect.Effect<void>;
}
>() {}
type LoggerLayer = (typeof Logger)['Service'];
// ----------------------------------------------
// define dependency constrained implementations
const ConsoleLoggerLive = Layer.succeed(Logger, {
info: (message: string) =>
pipe(
Effect.succeed(console.info(message)),
Effect.withSpan('logger-console/info'),
),
error: (message: string) =>
pipe(
Effect.succeed(console.error(message)),
Effect.withSpan('logger-console/error'),
),
});
const PinoLoggerLive = Layer.succeed(Logger, {
// define implementation for pino ...
});
// ----------------------------------------------
// consume layer
// const task: Effect.Effect<void, never, Logger>
const task = pipe(
Effect.gen(function* () {
const { error } = yield* Logger;
error('oh no!');
}),
);
// Providing logger implementation to run task
Effect.runSync(pipe(task, Effect.provide(ConsoleLoggerLive)));
// ----------------------------------------------
// creating a layer for tests
import { vi } from 'vitest';
type LoggerTestLayerInput = {
info?: Effect.Effect<void>;
error?: Effect.Effect<void>;
};
const makeLoggerTestLayer = (input: LoggerTestLayerInput) => {
const errorMock = vi.fn().mockReturnValue(input.error);
const infoMock = vi.fn().mockReturnValue(input.info);
const make: Partial<LoggerLayer> = {
error: errorMock,
info: infoMock,
};
return {
LoggerTestLayer: Layer.succeed(Logger, Logger.of(make as never)),
errorMock,
infoMock,
};
};
// ----------------------------------------------
// Using the layer in a test
import { expect, describe, it } from 'vitest';
describe("my effect task", () => {
it('should call error function', () => {
const { LoggerTestLayer, errorMock } = makeLoggerTestLayer({
error: Effect.void,
});
Effect.runSync(
pipe(
task, // Effect.Effect<void, never, Logger>
Effect.provide(LoggerTestLayer),
),
);
expect(errorMock).toHaveBeenCalledTimes(1);
expect(errorMock).toHaveBeenCalledWith('oh no!');
});
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment