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) 👧。 [![npm version](https://badge.fury.io/js/@adrien-may%2Ffactory.svg)](https://badge.fury.io/js/@adrien-may%2Ffactory) ![Build](https://static.pigsec.cn/wp-content/uploads/repos/2026/05/ae169d22dc054820.svg) [![codecov](https://codecov.io/gh/teamMay/factory/graph/badge.svg?token=CY75HN8SZS)](https://codecov.io/gh/teamMay/factory) [![License: ISC](https://img.shields.io/badge/License-ISC-blue.svg)](https://opensource.org/licenses/ISC) [![Open Source Love](https://badges.frapsoft.com/os/v3/open-source.svg?v=103)](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, 单元测试, 后端开发, 实体模型, 工厂模式, 开源库, 搜索引擎爬虫, 数据可视化, 数据库填充, 数据生成, 暗色界面, 测试夹具, 测试工厂, 软件开发, 软件测试