Skip to content

Commit

Permalink
Merge pull request #54 from w3tecch/feat/testing
Browse files Browse the repository at this point in the history
Feat/testing
  • Loading branch information
Gery Hirschfeld authored Apr 14, 2020
2 parents aadf783 + f3b0f2d commit 9f68031
Show file tree
Hide file tree
Showing 15 changed files with 279 additions and 141 deletions.
140 changes: 110 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,16 @@
- [Introduction](#-introduction)
- [Installation](#-installation)
- [Basic Seeder](#-basic-seeder)
- [Factory API](#-factory-api)
- [Using Entity Factory](#-using-entity-factory)
- [Seeding Data in Testing](#-seeding-data-in-testing)
- [Changelog](#-changelog)
- [License](#-license)

## ❯ Introduction

Isn't it exhausting to create some sample data for your database, well this time is over!

How does it work? Just create a factory for your entities (models) and a seed script.
How does it work? Just create a entity factory for your entities (models) and a seed script.

### Enity

Expand Down Expand Up @@ -74,9 +75,9 @@ export class Pet {

### Factory

The purpose of a factory is to create new fake entites with generate data.
Then for each entity define a factory. The purpose of a factory is to create new entites with generate data.

> Factories can also be used to generate test data for some unit, integration or e2e tests.
> Note: Factories can also be used to generate data for testing.
```typescript
// user.factory.ts
Expand Down Expand Up @@ -106,13 +107,16 @@ define(Pet, (faker: typeof Faker) => {

### Seeder

The seeder can be called by the configured cli command `seed:run`. In this case it generates 10 pets with a owner (User).

And last but not least, create a seeder. The seeder can be called by the configured cli command `seed:run`. In this case it generates 10 pets with a owner (User).

> Note: `seed:run` must be configured first. Go to [CLI Configuration](#cli-configuration).
```typescript
// create-pets.seed.ts
export default class CreatePets implements Seeder {
public async run(factory: Factory, connection: Connection): Promise<any> {
await factory(Pet)().seedMany(10)
await factory(Pet)().createMany(10)
}
}
```
Expand All @@ -121,15 +125,15 @@ export default class CreatePets implements Seeder {

Before using this TypeORM extension please read the [TypeORM Getting Started](https://typeorm.io/#/) documentation. This explains how to setup a TypeORM project.

You can install our extension with `npm` or `yarn`.
After that install the extension with `npm` or `yarn`.

```bash
npm i typeorm-seeding
# or
yarn add typeorm-seeding
```

Optional, for `Faker` types
Optional, install the type definitions of the `Faker` library.

```bash
npm install -D @types/faker
Expand All @@ -139,7 +143,7 @@ npm install -D @types/faker

To configure the path to your seeds and factories change the TypeORM config file(ormconfig.js or ormconfig.json).

> Default is `src/database/{seeds,factories}/**/*{.ts,.js}`
> The default paths are `src/database/{seeds,factories}/**/*{.ts,.js}`
**ormconfig.js**

Expand All @@ -160,7 +164,7 @@ TYPEORM_SEEDING_SEEDS=src/seeds/**/*{.ts,.js}

### CLI Configuration

Add the following scripts to your `package.json` file.
Add the following scripts to your `package.json` file to configure the seed cli commands.

```
"scripts": {
Expand All @@ -170,7 +174,9 @@ Add the following scripts to your `package.json` file.
}
```

> Now you are able to execute your seeds with this command `npm run seed:run`.
To execute the seed run `npm run seed:run` in the terminal.

> Note: More CLI optios are [here](#cli-options)
Add the following TypeORM cli commands to the package.json to drop and sync the database.

Expand All @@ -187,14 +193,16 @@ Add the following TypeORM cli commands to the package.json to drop and sync the

| Option | Default | Description |
| ---------------------- | --------------- | --------------------------------------------------------------------------- |
| `--seed` or `-s` | null | Option to specify a specific seeder class to run individually. |
| `--seed` or `-s` | null | Option to specify a seeder class to run individually. |
| `--connection` or `-c` | null | Name of the typeorm connection. Required if there are multiple connections. |
| `--configName` or `-n` | `ormconfig.js` | Name to the typeorm config file. |
| `--root` or `-r` | `process.cwd()` | Path to the typeorm config file. |

## ❯ Basic Seeder

The seeds files define how much and how the data are connected with each other. The files will be executed alphabetically.
A seeder class only contains one method by default `run`. Within this method, you may insert data into your database. For manually insertion use the [Query Builder](https://typeorm.io/#/select-query-builder) or use the [Entity Factory](#-using-entity-factory)

> Note. The seeder files will be executed alphabetically.
```typescript
import { Factory, Seeder } from 'typeorm-seeding'
Expand All @@ -216,15 +224,17 @@ export default class CreateUsers implements Seeder {
}
```

## ❯ Factory API
## ❯ Using Entity Factory

Of course, manually specifying the attributes for each entity seed is cumbersome. Instead, you can use entity factories to conveniently generate large amounts of database records.

For all entities we want to seed, we need to define a factory. To do so we give you the awesome [faker](https://github.com/marak/Faker.js/) library as a parameter into your factory. Then create your "fake" entity and return it. Those factory files should be in the `src/database/factories` folder and suffixed with `.factory` like `src/database/factories/user.factory.ts`.
For all entities we want to create, we need to define a factory. To do so we give you the awesome [faker](https://github.com/marak/Faker.js/) library as a parameter into your factory. Then create your "fake" entity and return it. Those factory files should be in the `src/database/factories` folder and suffixed with `.factory` like `src/database/factories/user.factory.ts`.

| Types | Description |
| --------------- | ----------------------------------------------------------------------------- |
| `Enity` | TypeORM Enity like the user or the pet in the samples. |
| `Context` | Argument to pass some static data into the factory function. |
| `EntityFactory` | This object is used to make new filled entities or seed it into the database. |
| Types | Description |
| --------------- | ------------------------------------------------------------------------------- |
| `Enity` | TypeORM Enity like the user or the pet in the samples. |
| `Context` | Argument to pass some static data into the factory function. |
| `EntityFactory` | This object is used to make new filled entities or create it into the database. |

### `define`

Expand Down Expand Up @@ -268,16 +278,16 @@ map(mapFunction: (entity: Entity) => Promise<Entity>): EntityFactory<Entity, Con
```typescript
await factory(User)()
.map(async (user: User) => {
const pets: Pet[] = await factory(Pet)().seedMany(2)
const pets: Pet[] = await factory(Pet)().createMany(2)
const petIds = pets.map((pet: Pet) => pet.Id)
await user.pets().attach(petIds)
})
.seedMany(5)
.createMany(5)
```

#### `make` & `makeMany`

Make and makeMany executes the factory functions and return a new instance of the given enity. The instance is filled with the generated values from the factory function.
Make and makeMany executes the factory functions and return a new instance of the given enity. The instance is filled with the generated values from the factory function, but not saved in the database.

**overrideParams** - Override some of the attributes of the enity.

Expand All @@ -294,23 +304,93 @@ await factory(User)().make({ email: '[email protected]' })
await factory(User)().makeMany(10, { email: '[email protected]' })
```

#### `seed` & `seedMany`
#### `create` & `createMany`

seed and seedMany is similar to the make and makeMany method, but at the end the created entity instance gets persisted in the database.
the create and createMany method is similar to the make and makeMany method, but at the end the created entity instance gets persisted in the database.

**overrideParams** - Override some of the attributes of the enity.

```typescript
seed(overrideParams: EntityProperty<Entity> = {}): Promise<Entity>
create(overrideParams: EntityProperty<Entity> = {}): Promise<Entity>
```

```typescript
await factory(User)().seed()
await factory(User)().seedMany(10)
await factory(User)().create()
await factory(User)().createMany(10)

// override the email
await factory(User)().seed({ email: '[email protected]' })
await factory(User)().seedMany(10, { email: '[email protected]' })
await factory(User)().create({ email: '[email protected]' })
await factory(User)().createMany(10, { email: '[email protected]' })
```

## ❯ Seeding Data in Testing

The entity factories can also be used in testing. To do so call the `useSeeding` function, which loads all the defined entity factories.

> Note: Normally Jest parallelizes test runs, which all conect to the same database. This could lead to strange sideeffects. So use the `--runInBand` flag to disable parallelizes runs.
```typescript
describe("UserService", () => {
beforeAll(async (done) => {
await useRefreshDatabase()
await useSeeding()

const user = await factory(User)().make()
const createdUser = await factory(User)().create()

await runSeeder(CreateUserSeed)
done()
})

afterAll(async (done) => {
await tearDownDatabase()
done()
})

test('Should ...', () => { ... })
})
```

### `useSeeding`

Loads the defined entity factories.

```typescript
useSeeding(options: ConfigureOption = {}): Promise<void>
```

### `runSeeder`

Runs the given seeder class.

```typescript
useSeeding(seed: SeederConstructor): Promise<void>
```

### `useRefreshDatabase`

Connects to the database, drops it and recreates the schema.

```typescript
useRefreshDatabase(options: ConfigureOption = {}): Promise<Connection>
```

### `tearDownDatabase`

Closes the open database connection.

```typescript
tearDownDatabase(): Promise<void>
```

### `ConfigureOption`

```typescript
interface ConfigureOption {
root?: string // path to the orm config file. Default = process.cwd()
configName?: string // name of the config file. eg. ormconfig.js
connection?: string // name of the database connection.
}
```

## ❯ Changelog
Expand Down
4 changes: 4 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
};
Binary file modified logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 0 additions & 2 deletions ormconfig.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
const path = require('path')

module.exports = [
{
name: 'sample',
Expand Down
18 changes: 2 additions & 16 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "typeorm-seeding",
"version": "1.4.3",
"version": "1.4.4",
"description": "🌱 A delightful way to seed test data into your database.",
"license": "MIT",
"author": "Gery Hirschfeld <[email protected]> (https://github.com/hirsch88)",
Expand All @@ -17,7 +17,7 @@
"prebuild": "rimraf dist",
"format": "prettier --write \"src/**/*.ts\"",
"lint": "eslint \"src/**/*.ts\" --fix",
"build": "tsc --project ./tsconfig.build.json",
"build": "npm run prebuild && tsc --project ./tsconfig.build.json",
"watch": "rollup -cw",
"test": "jest",
"test:watch": "jest --watch",
Expand Down Expand Up @@ -67,19 +67,5 @@
},
"resolutions": {
"mem": ">=4.0.0"
},
"jest": {
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "src",
"testRegex": ".test.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"coverageDirectory": "../coverage",
"testEnvironment": "node"
}
}
2 changes: 1 addition & 1 deletion sample/seeds/create-pets.seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ import { Pet } from '../entities/Pet.entity'

export default class CreatePets implements Seeder {
public async run(factory: Factory, connection: Connection): Promise<any> {
await factory(Pet)().seed()
await factory(Pet)().create()
}
}
2 changes: 1 addition & 1 deletion sample/seeds/create-users.seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ import { User } from '../entities/User.entity'

export default class CreateUsers implements Seeder {
public async run(factory: Factory): Promise<void> {
await factory(User)({ roles: [] }).seedMany(10)
await factory(User)({ roles: [] }).createMany(10)
}
}
17 changes: 8 additions & 9 deletions src/commands/config.command.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as yargs from 'yargs'
import * as chalk from 'chalk'
import { getConnectionOption } from '../typeorm-seeding'
import { printError } from '../utils/log.util'
import { configureConnection, getConnectionOptions } from '../connection'

export class ConfigCommand implements yargs.CommandModule {
command = 'config'
Expand Down Expand Up @@ -29,15 +29,14 @@ export class ConfigCommand implements yargs.CommandModule {
async handler(args: yargs.Arguments) {
const log = console.log
const pkg = require('../../package.json')
log('🌱 ' + chalk.bold(`TypeORM Seeding v${(pkg as any).version}`))
log('🌱 ' + chalk.bold(`TypeORM Seeding v${(pkg as any).version}`))
try {
const option = await getConnectionOption(
{
root: args.root as string,
configName: args.configName as string,
},
args.connection as string,
)
configureConnection({
root: args.root as string,
configName: args.configName as string,
connection: args.connection as string,
})
const option = await getConnectionOptions()
log(option)
} catch (error) {
printError('Could not find the orm config file', error)
Expand Down
Loading

0 comments on commit 9f68031

Please sign in to comment.