npm package: @nickglenn/mongo-seeder
Description: Test utility for seeding MongoDB with application data.
Author: Nick Glenn
Latest version: 2.0.1

## NodeJS Mongo Seeders for Testing

This library provides methods for quickly creating utilities for inserting seeded data into MongoDB for testing purposes. It supports several methods for inserting one to many rows, applying "patch" data on top of seed generated data, and clean up for seeded data.

## Getting Started

Install the library via `npm`. It's recommended that you install this as a development dependency.

```
npm i -D @nickglenn/mongo-seeder
```

Once installed, you can start integrating it by creating a "seeder map". This requires an object where the `key` represents a collection in your database, and the `value` is a factory function that returns the data necessary for a single document. It's recommended that you randomize the data output from each factory function in the map as this will help to create more robust tests.

> **Note:** In the example below, we use the [`faker`](https://www.npmjs.com/package/faker) library to create some randomized data for our tests.

```ts
import { createSeederMap } from "@nickglenn/mongo-seeder";
import * as faker from "faker";

export const setupSeeders = createSeederMap({
  users: () => ({
    email: faker.internet.email(),
    password: faker.internet.password(),
  }),
  posts: () => ({
    title: faker.lorem.words(3),
    content: faker.lorem.sentences(5),
    published: faker.random.boolean(),
  }),
});
```

Now let's add it to our test suite. The `createSeederMap` function returned another function that needs us to provide an instance of a Mongo Database class. Once we've done that, we can begin using our seeders in some tests.

> **Note:** The following syntax is what you'd likely see in a Jest or Mocha suite.

```ts
import * as mongo from "mongodb";
import { setupSeeders } from "./mySeeders";
import { findUserByEmail } from "./some/path/in/app";

let seeders;

beforeAll(async () => {
  // connect to mongodb
  const conn = await mongo.connect("mongodb://localhost/testing");

  // instantiate our seeders
  seeders = setupSeeders(conn.db());
});

afterEach(async () => {
  // after each test we can destroy all seeded data generated by each test
  await seeders.clean();
});

it("correctly fetches a user based on email", async () => {
  // use any of the seeder methods to insert some test records
  const testUser = await seeders.users.one();

  // run your code
  const fetchedUser = await findUserByEmail(testUser.email);

  // perform assertions
  expect(testUser._id.equals(fetchedUser._id)).toBe(true);
});
```

## Seed Patching

```ts
type SeedPatcher<T> = Partial<T> | (generated: T, index: number) => T;
```

All of the methods for inserting seeds into the database support "seed patching". This allows generated records to be augmented in order to support specific test case scenarios.

### Patch via Object

Patching seeds using an object performs a deep merge of the patch value on top of the generated value. This allows you to override the generated data before it's put into the database.

Using the example from above, we could alter our test to use a specific email address.

```ts
it("correctly fetches a user based on email", async () => {
  // use any of the seeder methods to insert some test records
  const testUser = await seeders.users.one({ email: "hello@example.com" });

  // run your code
  const fetchedUser = await findUserByEmail("hello@example.com");

  // perform assertions
  expect(testUser._id.equals(fetchedUser._id)).toBe(true);
});
```

### Patch via Function

Sometimes a bit more flexibility is needed for testing scenarios. A patching function is called with the generated seed value (and numeric index) and the returned value is what will be stored in the database before being returned.

```ts
it("correctly fetches a user based on email from a larger pool of users", async () => {
  // create 5 user records, but only the second record will have the test email
  const testUsers = await seeders.users.many(5, (user, index) => {
    if (index === 1) {
      return { ...user, email: "hello@example.com" };
    }
    return user;
  });

  // run your code
  const fetchedUser = await findUserByEmail("hello@example.com");

  // perform assertions
  expect(testUsers[0]._id.equals(fetchedUser._id)).toBe(false);
  expect(testUsers[1]._id.equals(fetchedUser._id)).toBe(true);
  expect(testUsers[2]._id.equals(fetchedUser._id)).toBe(false);
  expect(testUsers[3]._id.equals(fetchedUser._id)).toBe(false);
  expect(testUsers[4]._id.equals(fetchedUser._id)).toBe(false);
});
```

## Seeder Methods

### one

```ts
Seeder<T>.one(patch?: SeedPatcher<T>): Promise<T>;
```

Creates and inserts a single record into the database, returning the inserted record.

### many

```ts
Seeder<T>.many(count: number, patch?: SeedPatcher<T>): Promise<T[]>;
```

Creates and inserts 1-n records into the database, returning the inserted records as an array. The argument `count` cannot be less than `1` and must be an integer value.

### random

```ts
Seeder<T>.random(min: number, max: number, patch?: SeedPatcher<T>): Promise<T[]>;
```

Creates and inserts a random number of records (within the given range) into the database, returning the inserted records as an array. The `min` argument must be `0` or greater and the `max` argument must be `1` greater than `min`.

### pick

```ts
Seeder<T>.pick(min: number, max: number, patch: SeedPatcher<T>): Promise<[ T, T[] ]>;
```

An amalgamation of the other seeder records, `pick` actually **requires** that a seed patcher be provided. This is because pick will create a random number of records (within the given range) and insert them into the database However, a randomly selected record will be selected as the `standout` and will be patched, while the other records _will not be patched_ and are stored in a `crowds` array.

This is useful for testing operations that may have differing results based on the amount of "noise" in the database.

```ts
const [standout, crowd] = await seeder.users.pick(min: 2, max: 20, { email: "standout@example.com" });
```

### clean

```ts
Seeder<T>.clean(): Promise<void>;
```

Deletes all inserted records created by the seeder instance.