-
-
Notifications
You must be signed in to change notification settings - Fork 499
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore(deps): update dependency eslint-plugin-jsdoc to v32.0.2 (#1453)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
- Loading branch information
Showing
16 changed files
with
610 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
--- | ||
title: Seeding | ||
--- | ||
|
||
When initializing your application or testing it can be exhausting to create sample data for your database. The solution is to use seeding. Create factories for your entities and use them in the seed script or combine multiple seed scripts. | ||
|
||
## Seeders | ||
A seeder class contains one method `run`. This method is called when you use the command `mikro-orm db:seed`. In the `run` method you define how and what data you want to insert into the database. You can create entities using the [EntityManager](http://mikro-orm.io/docs/entity-manager) or you can use [Factories](#using-entity-factories). | ||
|
||
As an example we will look at a very basic seeder. | ||
```typescript | ||
import { EntityManager } from '@mikro-orm/core'; | ||
import { Seeder } from '@mikro-orm/seeder'; | ||
import { Author } from './author' | ||
|
||
class DatabaseSeeder extends Seeder { | ||
|
||
async run(em: EntityManager): Promise<void> { | ||
const author = em.create(Author, { | ||
name: 'John Snow', | ||
email: 'snow@wall.st' | ||
}); | ||
await em.persistAndFlush(author); | ||
em.clear(); | ||
} | ||
} | ||
``` | ||
|
||
### Using entity factories | ||
Instead of specifying all the attributes for every entity, you can also use [entity factories](#entity-factories). These can be used to generate large amounts of database records. Please read the [documentation on how to define factories](#entity-factories) to learn how to define your factories. | ||
|
||
As an example we will generate 10 authors. | ||
```typescript | ||
import { EntityManager } from '@mikro-orm/core'; | ||
import { Seeder } from '@mikro-orm/seeder'; | ||
import { AuthorFactory } from '../factories/author.factory' | ||
|
||
class DatabaseSeeder extends Seeder { | ||
|
||
async run(em: EntityManager): Promise<void> { | ||
await (new AuthorFactory(em)).count(10).create() | ||
em.clear(); | ||
} | ||
} | ||
``` | ||
|
||
### Calling additional seeders | ||
Inside the `run` method you can specify other seeder classes. You can use the `call` method to breakup the database seeder into multiple files to prevent a seeder file from becoming too large. The `call` method accepts an array of seeder classes. | ||
```typescript | ||
import { EntityManager } from '@mikro-orm/core'; | ||
import { Seeder } from '@mikro-orm/seeder'; | ||
import { AuthorSeeder, BookSeeder } from '../seeders' | ||
|
||
class DatabaseSeeder extends Seeder { | ||
|
||
run(em: EntityManager): Promise<void> { | ||
return this.call(em, [ | ||
AuthorSeeder, | ||
BookSeeder | ||
]); | ||
} | ||
} | ||
``` | ||
|
||
## Entity factories | ||
When testing you may insert entities in the database before starting a test. Instead of specifying every attribute of every entity by hand, you could also use a `Factory` to define a set of default attributes for an entity using entity factories. | ||
|
||
Lets have a look at an example factory for an [Author entity](http://mikro-orm.io/docs/defining-entities). | ||
```typescript | ||
import { Factory } from '@mikro-orm/seeder'; | ||
import * as Faker from 'faker'; | ||
import { Author } from './entities/author.entity'; | ||
|
||
export class AuthorFactory extends Factory<Author> { | ||
model = Author; | ||
|
||
definition(faker: typeof Faker): Partial<Author> { | ||
return { | ||
name: faker.person.findName(), | ||
email: faker.internet.email(), | ||
age: faker.random.number(18, 99) | ||
}; | ||
} | ||
} | ||
``` | ||
Basically you extend the base `Factory` class, define a `model` property and a `definition` method. The `model` defines for which entity the factory generates entity instances. The `definition` method returns the default set of attribute values that should be applied when creating an entity using the factory. | ||
|
||
Via the faker property, factories have access to the [Faker library](https://www.npmjs.com/package/faker), which allows you to conveniently generate various kinds of random data for testing. | ||
|
||
### Creating entities using factories | ||
Once you defined your factories you can use them to generate entities. Simply import the factory, instantiate it and call the `make` or `create` method. | ||
```typescript | ||
const author = await (new AuthorFactory(orm.em)).make() as Author; | ||
``` | ||
The `make` method returns the type `Author | Author[]`, so be sure to cast it to the correct type. | ||
|
||
#### Generate multiple entities | ||
Generate multiple entities by chaining the `count` and `make` method. The parameter of the `count` method is the number of entities you generate. | ||
```typescript | ||
// Generate 5 authors | ||
const authors = await (new AuthorFactory(orm.em)).count(5).make() as Author[]; | ||
``` | ||
The `make` method returns the type `Author | Author[]`, since we chained the `count` method here with `5` we can be sure it returns `Author[]`. | ||
|
||
#### Overriding attributes | ||
If you would like to override some of the default values of your factories, you may pass an object to the make method. Only the specified attributes will be replaced while the rest of the attributes remain set to their default values as specified by the factory. | ||
```typescript | ||
const author = await (new AuthorFactory(orm.em)).make({ | ||
name: 'John Snow' | ||
}) as Author; | ||
``` | ||
|
||
### Persisting entities | ||
The `create` method instantiates entities and persists them to the database using the `persistAndFlush` method of the EntityManager. | ||
```typescript | ||
// Make and persist a single author | ||
const author = await (new AuthorFactory(orm.em)).make() as Author; | ||
|
||
// Make and persist 5 authors | ||
const authors = await (new AuthorFactory(orm.em)).count(5).create() as Author[]; | ||
``` | ||
You can override the default values of your factories by passing an object to the `create` method. | ||
```typescript | ||
// Make and persist a single author | ||
const author = await (new AuthorFactory(orm.em)).create({ | ||
name: 'John Snow' | ||
}) as Author; | ||
``` | ||
### Factory relationships | ||
It is nice to create large quantities of data for one entity, but most of the time we want to create data for multiple entities and also have relations between these. For this we can use the `map` method which can be chained on a factory. The `map` method can be called with a function that maps the output entity from the factory before returning it. Lets look at some examples for the different relations. | ||
|
||
#### ManyToOne and OneToOne relations | ||
```typescript | ||
const book = await (new BookFactory(orm.em)).map(async (book) => { | ||
book.author = await (new AuthorFactory(orm.em)).make() as Author; | ||
return book; | ||
}) as Book; | ||
``` | ||
|
||
#### OneToMany and ManyToMany | ||
```typescript | ||
const book = await (new BookFactory(orm.em)).map(async (book) => { | ||
book.owners = new Collection(book, await (new OwnerFactory(orm.em)).make() as Owner[]); | ||
return book; | ||
}) as Book; | ||
``` | ||
|
||
## Use in tests | ||
Now we know how to create seeders and factories, but how can we effectively use them in tests. We will show an example how it can be used. | ||
|
||
```typescript | ||
let seeder: MikroOrmSeeder; | ||
|
||
beforeAll(async () => { | ||
// Initialize seeder with config | ||
seeder = await MikroOrmSeeder.init(mikroOrmConfig); | ||
|
||
// Refresh the database to start clean | ||
await seeder.refreshDatabase(); | ||
|
||
// Seed using a seeder defined by you | ||
await seeder.seed(DatabaseSeeder); | ||
}) | ||
|
||
test(() => { | ||
// Do tests | ||
}); | ||
|
||
afterAll(() => { | ||
// Close connection | ||
seeder.closeConnection(); | ||
}) | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
{ | ||
"name": "@mikro-orm/seeder", | ||
"version": "4.4.2", | ||
"description": "Seeder package for Mikro-ORM.", | ||
"main": "dist/index.js", | ||
"typings": "dist/index.d.ts", | ||
"repository": { | ||
"type": "git", | ||
"url": "git+ssh://git@github.com/mikro-orm/mikro-orm.git" | ||
}, | ||
"keywords": [ | ||
"orm", | ||
"mongo", | ||
"mongodb", | ||
"mysql", | ||
"mariadb", | ||
"postgresql", | ||
"sqlite", | ||
"sqlite3", | ||
"ts", | ||
"typescript", | ||
"js", | ||
"javascript", | ||
"entity", | ||
"ddd", | ||
"mikro-orm", | ||
"unit-of-work", | ||
"data-mapper", | ||
"identity-map", | ||
"seeder" | ||
], | ||
"author": "Wybren Kortstra", | ||
"license": "MIT", | ||
"bugs": { | ||
"url": "https://github.com/mikro-orm/mikro-orm/issues" | ||
}, | ||
"homepage": "https://mikro-orm.io", | ||
"engines": { | ||
"node": ">= 10.13.0" | ||
}, | ||
"scripts": { | ||
"build": "yarn clean && yarn compile && yarn copy", | ||
"clean": "rimraf ./dist", | ||
"compile": "tsc -p tsconfig.build.json", | ||
"copy": "ts-node -T ../../scripts/copy.ts" | ||
}, | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"dependencies": { | ||
"@mikro-orm/core": "^4.4.2", | ||
"faker": "^5.4.0" | ||
}, | ||
"devDependencies": { | ||
"@types/faker": "^5.1.6" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import * as Faker from 'faker'; | ||
import { EntityManager } from '@mikro-orm/core'; | ||
|
||
export abstract class Factory<C> { | ||
|
||
abstract readonly model: { new(): C }; | ||
private amount = 1; | ||
private mapMethod?: (entity: C) => Promise<C>; | ||
|
||
constructor(private em: EntityManager) { | ||
} | ||
|
||
protected abstract definition(faker: typeof Faker): Partial<C>; | ||
|
||
public async make(overrideParameters?: Partial<C>): Promise<C | C[]> { | ||
const objects = [...Array(this.amount)].map(() => { | ||
const object = this.em.create(this.model, Object.assign({}, this.definition(Faker), overrideParameters)); | ||
if (this.mapMethod) { | ||
return this.mapMethod(object); | ||
} | ||
return object; | ||
}); | ||
const entities = await Promise.all(objects); | ||
return this.amount === 1 ? entities[0] : entities; | ||
} | ||
|
||
public async create(overrideParameters?: Partial<C>): Promise<C | C[]> { | ||
const objects = await this.make(overrideParameters); | ||
await this.em.persistAndFlush(objects); | ||
return objects; | ||
} | ||
|
||
public count(amount: number): Factory<C> { | ||
this.amount = amount; | ||
return this; | ||
} | ||
|
||
public map(mapMethod: (entity: C) => Promise<C>): Factory<C> { | ||
this.mapMethod = mapMethod; | ||
return this; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
/** | ||
* @packageDocumentation | ||
* @module seeder | ||
*/ | ||
export * from './seeder'; | ||
export * from './factory'; | ||
export * from './mikro-orm-seeder'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import { MikroORM, MikroORMOptions } from '@mikro-orm/core'; | ||
import { Seeder } from './seeder'; | ||
|
||
export class MikroOrmSeeder { | ||
|
||
orm!: MikroORM; | ||
|
||
static async init(config: MikroORMOptions): Promise<MikroOrmSeeder> { | ||
const seeder = new MikroOrmSeeder(); | ||
seeder.orm = await MikroORM.init(config); | ||
return seeder; | ||
} | ||
|
||
async refreshDatabase(): Promise<void> { | ||
const generator = this.orm.getSchemaGenerator(); | ||
await generator.dropSchema(); | ||
await generator.createSchema(); | ||
} | ||
|
||
async seed(...seederClasses: {new(): Seeder}[]): Promise<void> { | ||
for (const seederClass of seederClasses) { | ||
const seeder = new seederClass(); | ||
await seeder.run(this.orm.em); | ||
} | ||
} | ||
|
||
closeConnection(force = false): Promise<void> { | ||
return this.orm.close(force); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { EntityManager } from '@mikro-orm/core'; | ||
|
||
export abstract class Seeder { | ||
|
||
public abstract run(em: EntityManager): Promise<void>; | ||
|
||
protected call(em: EntityManager, seeders: {new(): Seeder}[]): Promise<void> { | ||
return new Promise<void>((resolve, reject) => { | ||
Promise.all(seeders.map(s => { | ||
return (new s()).run(em.fork()); | ||
})).then(() => { | ||
resolve(); | ||
}).catch(e => { | ||
reject(e); | ||
}); | ||
}); | ||
} | ||
|
||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
{ | ||
"extends": "../../tsconfig.build.json", | ||
"compilerOptions": { | ||
"outDir": "./dist" | ||
}, | ||
"include": ["src/**/*"] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
{ | ||
"extends": "../../tsconfig.json", | ||
"include": ["src/**/*"] | ||
} |
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { Entity, ManyToOne, OneToMany, PrimaryKey, Property } from '@mikro-orm/core'; | ||
import { Project } from './project.entity'; | ||
|
||
@Entity() | ||
export class House { | ||
@PrimaryKey() | ||
id!: number; | ||
|
||
@Property() | ||
address!: string; | ||
|
||
@Property() | ||
bought = false; | ||
|
||
@ManyToOne() | ||
project!: Project; | ||
|
||
@Property() | ||
createdAt = new Date(); | ||
} |
Oops, something went wrong.