@effit/testing
v0.1.0-alpha.7
Published
Testing primitives for the Effit framework.
Downloads
1,124
Readme
@effit/testing
Testing primitives for the Effit framework.
Testing.layer(layer)+it.effect/it.scoped—node:testadapter that runs each test under an Effect program with a shared layer (modeled after@effect/vitest)TestContainers.Postgres.Default/TestContainers.Redis.Default—Effect.Services that boot real containers via testcontainers and exposePG*/REDIS_*env vars to the rest of the layerMigrateDatabase.Default— runsknex migrate:latestinside the test scope (requiresKnexto be provided)CleanState.knex(config)/CleanState.redis({ host, port })— truncate DB + flush Redis between testsJobRunner.run(Job, data)— execute a@effit/workersJob's handler in-process against an integration test layerProcessorRunner.run(Processor, event)— run a projector or reactor against the event store inside a test scope
Installation
pnpm add -D @effit/testing @effit/event-sourcing @effit/workers @effit/knex @effit/core effect \
@testcontainers/postgresql @testcontainers/redis ioredisWiring a full integration layer
Set NODE_ENV=test once (either via the test script or at the top of the file that exports your integration helper) so AppConfig selects the test branch. Put everything else test-specific inside that test-branch EnvironmentConfig layer — it builds only when NODE_ENV='test', so the side effects are scoped to actual test runs:
// src/_config/environments/test.ts
import { Effect, Layer } from 'effect';
import { EnvironmentConfig } from './EnvironmentConfig.js';
export const TestConfig = Layer.effect(
EnvironmentConfig,
Effect.sync(() => {
process.env.JWT_SECRET = 'test-jwt-secret';
process.env.EMAIL_FROM = '[email protected]';
// ... any other test-only env
return {
nodeEnv: 'test',
server: { secureCookies: false },
database: { debug: false },
};
})
);// src/_lib/testing/integration/IntegrationTestLayer.ts
import { Layer, pipe } from 'effect';
import { TestContainers } from '@effit/testing/TestContainers.js';
import { MigrateDatabase } from '@effit/testing/MigrateDatabase.js';
import { ApplicationLive } from '#app/container.js';
import { Knex } from '#app/infra/knex/Knex.js';
process.env.NODE_ENV = 'test';
export const IntegrationTestLayer = pipe(
Layer.mergeAll(ApplicationLive, MigrateDatabase.Default),
Layer.provide(Knex.Default),
Layer.provide(TestContainers.Postgres.Default),
Layer.provide(TestContainers.Redis.Default),
);Testcontainers write PG* / REDIS_* during layer construction — don't hard-code those inside TestConfig; they'd just be overwritten.
// src/_lib/testing/integration/IntegrationTestCleanState.ts
import { Effect } from 'effect';
import { CleanState } from '@effit/testing/CleanState.js';
import knexConfig from '#app/infra/knex/knexfile.js';
export namespace IntegrationTestCleanState {
// Effect.suspend ensures process.env.REDIS_* is read at execution time
// (after testcontainers boot), not at module load.
export const clean = Effect.suspend(() =>
Effect.all(
[
CleanState.knex(knexConfig.test),
CleanState.redis({
host: process.env.REDIS_HOST!,
port: Number(process.env.REDIS_PORT),
}),
],
{ concurrency: 'unbounded' },
),
);
}Writing a test
import { Effect } from 'effect';
import { Testing } from '@effit/testing/Testing.js';
import { IntegrationTestLayer } from './IntegrationTestLayer.js';
import { IntegrationTestCleanState } from './IntegrationTestCleanState.js';
const integration = Testing.layer(IntegrationTestLayer);
integration('CreateTodo', (it) => {
it.beforeEach.effect(() => IntegrationTestCleanState.clean);
it.scoped('appends TodoCreated to the aggregate stream', () =>
Effect.gen(function* () {
const createTodo = yield* CreateTodo;
const { todoId } = yield* createTodo({ title: 'Try effit' });
// assert against the event store or a projection
}),
);
});License
MIT © Talysson de Oliveira Cassiano
