diff --git a/README.md b/README.md index 9eb4a8a..2538bf0 100644 --- a/README.md +++ b/README.md @@ -97,6 +97,92 @@ test("foo bar", async (t) => { ## Advanced Usage +### Postgres container de-duping + +In rare cases, you may want to spawn more than one Postgres container. + +Internally, this library uses an AVA "shared worker". A shared worker is a singleton shared with the entire running test suite, and so one `ava-postgres` shared worker maps to exactly one Postgres container. + +To spawn separate shared workers and thus additional Postgres containers, you have two options: + +**Specify different version strings for the `postgresVersion` option in the factory function**: + +```ts +const getTestPostgresDatabase = getTestPostgresDatabaseFactory({ + postgresVersion: "14", +}) +``` + +Each unique version will map to a unique shared worker. + +**Set the `workerDedupeKey` option in the factory function**: + +```ts +const getTestPostgresDatabase = getTestPostgresDatabaseFactory({ + workerDedupeKey: "foo", +}) +``` + +Each unique key will map to a unique shared worker. + +### Database de-duping + +By default, `ava-postgres` will create a new database for each test. If you want to share a database between tests, you can use the `databaseDedupeKey` option: + +```ts +import test from "ava" +const getTestPostgresDatabase = getTestPostgresDatabaseFactory({}) + +test("foo", async (t) => { + const connection1 = await getTestPostgresDatabase(t, null, { + databaseDedupeKey: "foo", + }) + const connection2 = await getTestPostgresDatabase(t, null, { + databaseDedupeKey: "foo", + }) + t.is(connection1.database, connection2.database) +}) +``` + +This works across the entire test suite. + +Note that if unique parameters are passed to the `beforeTemplateIsBaked` (`null` in the above example), separate databases will still be created. + +### "Nested" `beforeTemplateIsBaked` calls + +In some cases, if you do extensive setup in your `beforeTemplateIsBaked` hook, you might want to obtain a separate, additional database within it if your application uses several databases for different purposes. This is possible by using the passed `beforeTemplateIsBaked` to your hook callback: + +```ts +type DatabaseParams = { + type: "foo" | "bar" +} + +const getTestServer = getTestPostgresDatabaseFactory({ + beforeTemplateIsBaked: async ({ + params, + connection: { pool }, + beforeTemplateIsBaked, + }) => { + if (params.type === "foo") { + await pool.query(`CREATE TABLE "foo" ("id" SERIAL PRIMARY KEY)`) + // Important: return early to avoid infinite loop + return + } + + await pool.query(`CREATE TABLE "bar" ("id" SERIAL PRIMARY KEY)`) + // This created database will be torn down at the end of the top-level `beforeTemplateIsBaked` call + const fooDatabase = await beforeTemplateIsBaked({ + params: { type: "foo" }, + }) + + // This works now + await fooDatabase.pool.query(`INSERT INTO "foo" DEFAULT VALUES`) + }, +}) +``` + +Be very careful when using this to avoid infinite loops. + ### Bind mounts & `exec`ing in the container `ava-postgres` uses [testcontainers](https://www.npmjs.com/package/testcontainers) under the hood to manage the Postgres container. diff --git a/src/public-types.ts b/src/public-types.ts index 038a25e..cbd1c20 100644 --- a/src/public-types.ts +++ b/src/public-types.ts @@ -48,13 +48,50 @@ export interface GetTestPostgresDatabaseFactoryOptions< } /** - * Test workers will be de-duped by this key. You probably don't need to set this. + * In rare cases, you may want to spawn more than one Postgres container. + * Internally, this library uses an AVA "shared worker". A shared worker is a singleton shared with the entire running test suite, and so one `ava-postgres` shared worker maps to exactly one Postgres container. + * To spawn separate shared workers and thus additional Postgres containers, you can specify a custom key here. + * Each unique key will map to a unique shared worker/unique Postgres container. */ workerDedupeKey?: string beforeTemplateIsBaked?: (options: { connection: ConnectionDetails params: Params containerExec: (command: string[]) => Promise + /** + * + * In some cases, if you do extensive setup in your `beforeTemplateIsBaked` hook, you might want to obtain a separate, additional database within it if your application uses several databases for different purposes. This is possible by using the passed `beforeTemplateIsBaked` to your hook callback. + * Be very careful when using this to avoid infinite loops. + * @example + * ```ts + * type DatabaseParams = { + * type: "foo" | "bar" + * } + + * const getTestServer = getTestPostgresDatabaseFactory({ + * beforeTemplateIsBaked: async ({ + * params, + * connection: { pool }, + * beforeTemplateIsBaked, + * }) => { + * if (params.type === "foo") { + * await pool.query(`CREATE TABLE "foo" ("id" SERIAL PRIMARY KEY)`) + * // Important: return early to avoid infinite loop + * return + * } + + * await pool.query(`CREATE TABLE "bar" ("id" SERIAL PRIMARY KEY)`) + * // This created database will be torn down at the end of the top-level `beforeTemplateIsBaked` call + * const fooDatabase = await beforeTemplateIsBaked({ + * params: { type: "foo" }, + * }) + + * // This works now + * await fooDatabase.pool.query(`INSERT INTO "foo" DEFAULT VALUES`) + * }, + * }) + * ``` + */ beforeTemplateIsBaked: ( options: { params: Params @@ -71,6 +108,28 @@ export type GetTestPostgresDatabaseOptions = { /** * If `getTestPostgresDatabase()` is called multiple times with the same `key` and `params`, the same database is guaranteed to be returned. */ + /** + * By default, `ava-postgres` will create a new database for each test. If you want to share a database between tests, you can use the `databaseDedupeKey` option. + * This works across the entire test suite. + * + * Note that if unique parameters are passed to the `beforeTemplateIsBaked` (`null` in the above example), separate databases will still be created. + * @example + * ```ts + * import test from "ava" + * + * const getTestPostgresDatabase = getTestPostgresDatabaseFactory({}) + * + * test("foo", async (t) => { + * const connection1 = await getTestPostgresDatabase(t, null, { + * databaseDedupeKey: "foo", + * }) + * const connection2 = await getTestPostgresDatabase(t, null, { + * databaseDedupeKey: "foo", + * }) + * t.is(connection1.database, connection2.database) + * }) + * ``` + */ databaseDedupeKey?: string }