teamMay/factory
GitHub: teamMay/factory
一个受 Factory Boy 启发的 TypeScript 工厂模式库,用于在测试和数据填充场景中快速生成结构化的实体数据。
Stars: 7 | Forks: 1
# 工厂 🏭
这个包通过为你的实体/模型创建工厂的方式,让测试变得更简单。灵感来源于 [Factory Boy](https://github.com/FactoryBoy/factory_boy) 👦 Python 包和 [Factory Girl](https://github.com/simonexmachina/factory-girl)
👧。
[](https://badge.fury.io/js/@adrien-may%2Ffactory)

[](https://codecov.io/gh/teamMay/factory)
[](https://opensource.org/licenses/ISC)
[](https://github.com/ellerbrock/open-source-badges/)
你也可以看看这些项目:
- [typeorm-seeding](https://github.com/jorgebodega/typeorm-seeding)
- [typeorm-factories](https://github.com/owl1n/typeorm-factories)
- [typeorm-factory](https://github.com/linnify/typeorm-factory)
- [factory-girl-typeorm](https://github.com/wymsee/factory-girl-typeorm)
## 安装
**NPM**
```
npm install @adrien-may/factory --save-dev
```
**Yarn**
```
yarn add @adrien-may/factory --dev
```
## 用法
本节简要概述了你可以使用此包实现的功能。
### 工厂
#### 声明
要声明一个工厂,你必须提供:
- 一个 adapter:ObjectAdapter、TypeormAdapter 等
- 一个实体 [不使用 typeorm 时可选]:你正在构建的模型
- 需要填充的字段(它们的默认值或生成它们的方式)
adapter 允许你持久化你的数据。如果你想通过 typeorm 将数据保存到数据库中,你可以使用 `TypeormAdapter`。默认的 Adapter 是 `ObjectAdapter`,它不会持久化任何内容。你可以创建自己的 adapter,以你想要的方式持久化数据。
```
import { Factory } from '@adrien-may/factory';
// Object/Entity to create
class User {
email: string;
role: string;
}
// Factories
class UserFactory extends Factory {
entity = User;
attrs = {
email: 'adrien@factory.com',
role: 'basic',
};
// By default, adapter is ObjectAdapter
}
class AdminUserFactory extends Factory {
entity = User;
attrs = {
email: 'adrien@factory.com',
role: 'admin',
};
}
// Usage
const adminFactory = new AdminUserFactory();
const admin1 = adminFactory.create();
const admins = adminFactory.createMany(5);
```
##### Typeorm
```
import { Factory, TypeormFactory, TypeormAdapter } from '@adrien-may/factory';
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
const dataSource = new DataSource({
type: 'sqlite',
database: ':memory:',
... // other typeorm options
});
@Entity()
class User {
@PrimaryGeneratedColumn()
id: number;
@Column('text')
email: string;
}
export class UserFactory extends TypeormFactory {
entity = User;
attrs = {
email: 'adrien@factory.com',
};
}
// Usage
const userFactory = new UserFactory(dataSource);
const user = userFactory.create();
const users = userFactory.createMany(5);
```
##### 对象和接口
你可以直接使用没有定义实体的工厂,仅提供一个接口即可。
```
import { ObjectFactory } from '@adrien-may/factory';
interface User {
name: string;
}
class UserFactory extends ObjectFactory {
// No entity defined
attrs = {
name: 'John Doe',
};
}
// Usage
const userFactory = new UserFactory();
const user = userFactory.create();
const users = userFactory.createMany(5);
```
##### 自定义
你可能希望将 factory 与其他 ORM 或 CMS 一起使用。为了说明这一点,我们将演示如何为 payload cms 创建一个 factory
为此,你首先需要创建一个 adapter。
```
import { Adapter } from '@teammay/factory'
import {
DataFromCollectionSlug,
RequiredDataFromCollectionSlug,
} from 'node_modules/payload/dist/collections/config/types'
interface PayloadArgs {
draft: boolean;
locale: string
}
class PayloadAdapter extends Adapter {
constructor(
private payload: Payload,
private slug: S,
) {}
async save(
instance: RequiredDataFromCollectionSlug | RequiredDataFromCollectionSlug[],
{ draft = false, locale = 'en' }: PayloadArgs = { draft: false, locale: 'en' },
): Promise | DataFromCollectionSlug[]> {
const instances = Array.isArray(instance) ? instance : [instance]
const savedInstances = await Promise.all(
instances.map((data) =>
this.payload.create({
collection: this.slug,
data,
draft,
locale,
}),
),
)
return Array.isArray(instance) ? savedInstances : savedInstances[0]
}
}
```
现在有了这个 adapter,我们可以创建一个 PayloadFactory 类
```
import { Factory } from '@teammay/factory';
import {
DataFromCollectionSlug,
RequiredDataFromCollectionSlug,
} from 'node_modules/payload/dist/collections/config/types';
import { CollectionSlug, Payload } from 'payload';
abstract class PayloadFactory extends Factory, [PayloadArgs?]> {
constructor(
protected payload: Payload,
protected slug: S,
) {
super();
this.adapter = new PayloadAdapter(payload, slug);
}
}
```
有了这个类,我们现在可以为每个 payload 集合实例化工厂。例如,如果我们有一个别名为 'author' 且仅包含一个 'name' 字段的集合,我们会这样做:
```
interface User {
name: string;
}
class UserFactory extends PayloadFactory<'author'> {
// No entity defined
attrs = {
name: 'John Doe',
};
}
// Usage
const userFactory = new UserFactory();
const user = userFactory.create({}, { draft: true, locale: 'fr' });
const users = userFactory.createMany(5, {}, { draft: false, locale: 'es' });
```
#### 使用 Fakerjs/Chancejs 进行模糊生成
为了给我们的工厂生成伪随机数据,我们可以利用以下库:
- [Fakerjs](https://github.com/MilosPaunovic/community-faker)
- [Chancejs](https://chancejs.com)
- ...
```
import { TypeormFactory } from '@adrien-may/factory';
import Chance from 'chance';
const chance = new Chance();
export class ProfileFactory extends TypeormFactory {
entity = Profile;
attrs = {
name: () => chance.name(),
email: () => chance.email(),
description: () => chance.paragraph({ sentences: 5 }),
};
}
```
注意:此库中并未包含 Faker/Chance。我们仅仅是利用了每次创建工厂时都会调用传递给 `attrs` 的函数这一特性。因此,你可以使用 Faker/Chancejs 来生成数据。
#### 使用我们创建的工厂
我们可以使用工厂来创建实体的新实例:
```
const userFactory = new UserFactory();
```
对于 typeorm 工厂,你应该使用以下命令设置一个默认工厂:
```
import { setDefaultFactory } from '@adrien-may/factory';
{...}
setDefaultDataSource(typeormDatasource)
{...}
const userFactory = new UserFactory();
```
或者为每个实例设置一个 datasource:
```
const userFactory = new UserFactory(typeormDatasource);
```
工厂及其 adapter 暴露了一些函数:
- build:创建一个对象(并最终生成数据 / subFactories)
- create:与 make 相同,但通过 ORM 的 "save" 方法将对象持久化到数据库中
- buildMany 和 createMany 允许一次性创建多个实例
```
const user: User = await userFactory.create();
const users: User[] = await userFactory.createMany(5);
```
要覆盖工厂的默认属性,请将它们作为参数添加到 create 函数中:
```
const user: User = await userFactory.create({ email: 'adrien@example.com' });
```
### 子工厂
实体之间具有关联关系(manyToOne、oneToMany、oneToOne 等...)是相当常见的。在这种情况下,我们为所有实体创建工厂,并利用 `SubFactory` 在它们之间建立联系。SubFactories 将在实例被构建时进行解析。请注意,这只是一种语法糖,因为你可以使用调用另一个工厂的箭头函数来实现。
#### 示例
如果一个用户有一个与之关联的 profile 实体:我们在 `ProfileFactory` 上将 `UserFactory` 用作 SubFactory
```
import { Factory, SubFactory } from '@adrien-may/factory';
import { UseFactory } from './user.factory.ts'; // factory naming is free of convention here, don't worry about it.
export class ProfileFactory extends Factory {
entity = Profile;
attrs = {
name: 'Handsome name',
user: new SubFactory(UserFactory, { name: 'Override factory name' }),
};
}
```
### 序列
序列允许你在使用同一个工厂时,每次运行都获取一个递增的值。
这样,你就可以使用计数器来获得更有意义的数据。
```
import { Factory, Sequence } from '@adrien-may/factory';
export class UserFactory extends Factory {
entity = User;
attrs = {
customerId: new Sequence((nb: number) => `cust__abc__xyz__00${nb}`),
};
}
```
### 延迟属性
当你想根据正在创建的实例来生成一个值时,延迟属性非常有用。
它们会在所有其他属性之后、但在保存实体之前被解析。对于任何“保存后”的操作,请使用 PostGenerate 钩子。
```
import { Factory, LazyAttribute } from '@adrien-may/factory';
export class UserFactory extends Factory {
entity = User;
attrs = {
name: 'Sarah Connor',
mail: new LazyAttribute((instance) => `${instance.name.toLowerCase()}@skynet.org`),
};
}
```
### 延迟序列
延迟序列结合了序列和延迟属性的强大功能。该回调会被传入一个递增的数字和正在创建的实例作为参数。
```
import { Factory, LazySequence } from '@adrien-may/factory';
export class UserFactory extends Factory {
entity = User;
attrs = {
name: 'Sarah Connor',
mail: new LazySequence((nb, instance) => `${instance.name.toLowerCase()}.${nb}@skynet.org`),
};
}
```
### 后置生成
要在实例创建完成后执行操作,你可以使用 `PostGeneration` 装饰器。
```
import { Factory, PostGeneration } from '@adrien-may/factory';
export class UserFactory extends Factory {
...
@PostGeneration()
postGenerationFunction() {
// perform an action after creation
}
@PostGeneration()
async actionOnCreatedUser(user: User) {
// do something with user
}
}
```
#### 适配器
你可以提供自己的 adapter 来扩展此库。此库目前提供了 `ObjectAdapter`(默认)和 `TypeormAdapter`。
为了简化测试,此库提供了一个 `TypeormFactory` 类。
以下示例展示了如何使用它们:
```
import { Factory, TypeormFactory, TypeormAdapter } from '@adrien-may/factory';
export class UserFactory extends Factory {
entity = User;
attrs = {
email: 'adrien@factory.com',
role: 'basic',
};
adapter = TypeormAdapter();
}
// Same as:
export class TypeormUserFactory extends TypeormFactory {
entity = User;
attrs = {
email: 'adrien@factory.com',
role: 'admin',
};
}
```
### 数据填充 (NEW)
你现在可以使用 `runSeed` 工具轻松地通过你的工厂来填充数据库。
#### 示例
在你的项目中创建一个 `seed.ts` 文件:
```
import { runSeed } from '@adrien-may/factory';
import { UserFactory } from './factories/user.factory';
import { PostFactory } from './factories/post.factory';
import { dataSource } from './db'; // your TypeORM DataSource
export let fixtures: any = {};
async function main() {
fixtures = await runSeed({
dataSource,
factories: { UserFactory, PostFactory },
seeds: async ({ UserFactory, PostFactory }) => {
const users = await UserFactory.createMany(10);
const posts = await PostFactory.createMany(20);
return {
users,
posts,
};
},
});
}
main();
```
- 所有工厂都使用提供的 `dataSource` 实例化。
- 你可以在 `seeds` 函数内使用所有的工厂方法(create、createMany 等)。
- 此工具与框架无关,但与 TypeORM 配合使用效果最好。
- 你可以通过 `fixtures` 对象访问创建的实例。
#### 示例:在 Jest 设置中进行数据填充
你还可以在 Jest 设置中使用 `runSeed`,以确保在测试运行前测试数据库已被填充:
```
// jest.setup.ts
import { runSeed } from '@adrien-may/factory';
import { UserFactory } from './factories/user.factory';
import { dataSource } from './db';
beforeAll(async () => {
await runSeed({
dataSource,
factories: { UserFactory },
seeds: async ({ UserFactory }) => {
await UserFactory.createMany(5);
},
});
});
```
#### 示例:通过 npm 运行数据填充
将此添加到你的 `package.json` 脚本中:
```
"scripts": {
"seed": "ts-node ./seed.ts"
}
```
然后运行:
```
npm run seed
```
标签:CMS安全, GNU通用公共许可证, JavaScript, MITM代理, Mock数据, Node.js, NPM包, OSV-Scalibr, pocsuite3, TypeORM, 单元测试, 后端开发, 实体模型, 工厂模式, 开源库, 搜索引擎爬虫, 数据可视化, 数据库填充, 数据生成, 暗色界面, 测试夹具, 测试工厂, 软件开发, 软件测试