├── .commitlintrc ├── .git-blame-ignore-revs ├── .github ├── renovate.json └── workflows │ ├── commitlint.yml │ ├── coverage.yml │ ├── quality.yml │ └── release.yml ├── .gitignore ├── .node-version ├── .npmignore ├── .npmrc ├── .releaserc.json ├── .sgcrc ├── .vscode ├── extensions.json └── settings.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── biome.json ├── examples ├── 1-to-1-chained-related │ ├── README.md │ ├── dataSource.ts │ ├── entities │ │ ├── Pet.entity.ts │ │ ├── Refuge.entity.ts │ │ └── User.entity.ts │ ├── factories │ │ ├── Pet.factory.ts │ │ ├── Refuge.factory.ts │ │ └── User.factory.ts │ └── test │ │ ├── PetFactory.test.ts │ │ ├── RefugeFactory.test.ts │ │ └── UserFactory.test.ts ├── 1-to-1-nullable-related │ ├── README.md │ ├── dataSource.ts │ ├── entities │ │ ├── Pet.entity.ts │ │ └── User.entity.ts │ ├── factories │ │ ├── Pet.factory.ts │ │ └── User.factory.ts │ └── test │ │ ├── PetFactory.test.ts │ │ └── UserFactory.test.ts ├── 1-to-1-related │ ├── README.md │ ├── dataSource.ts │ ├── entities │ │ ├── Pet.entity.ts │ │ └── User.entity.ts │ ├── factories │ │ ├── Pet.factory.ts │ │ └── User.factory.ts │ └── test │ │ ├── PetFactory.test.ts │ │ └── UserFactory.test.ts ├── 1-to-N-related │ ├── README.md │ ├── dataSource.ts │ ├── entities │ │ ├── Pet.entity.ts │ │ └── User.entity.ts │ ├── factories │ │ ├── Pet.factory.ts │ │ └── User.factory.ts │ └── test │ │ ├── PetFactory.test.ts │ │ └── UserFactory.test.ts ├── N-to-M-related │ ├── README.md │ ├── dataSource.ts │ ├── entities │ │ ├── Pet.entity.ts │ │ └── User.entity.ts │ ├── factories │ │ ├── Pet.factory.ts │ │ └── User.factory.ts │ └── test │ │ ├── PetFactory.test.ts │ │ └── UserFactory.test.ts └── single-entity │ ├── README.md │ ├── dataSource.ts │ ├── entities │ └── User.entity.ts │ ├── factories │ └── User.factory.ts │ └── test │ └── UserFactory.test.ts ├── jest.config.ts ├── package.json ├── pnpm-lock.yaml ├── src ├── factory.ts ├── index.ts ├── instanceAttributes │ ├── eagerInstanceAttribute.ts │ ├── index.ts │ ├── instanceAttribute.ts │ └── lazyInstanceAttribute.ts ├── subfactories │ ├── baseSubfactory.ts │ ├── collectionSubfactory.ts │ ├── index.ts │ └── singleSubfactory.ts └── types.ts ├── test ├── factory.test.ts └── fixtures │ ├── Pet.entity.ts │ ├── Pet.factory.ts │ ├── User.entity.ts │ ├── User.factory.ts │ └── dataSource.ts ├── tsconfig.build.json └── tsconfig.json /.commitlintrc: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/commitlintrc.json", 3 | "extends": [ 4 | "@commitlint/config-conventional" 5 | ], 6 | "rules": { 7 | "body-max-line-length": [ 8 | 2, 9 | "always", 10 | 200 11 | ], 12 | "footer-max-line-length": [ 13 | 2, 14 | "always", 15 | "Infinity" 16 | ], 17 | "header-max-length": [ 18 | 2, 19 | "always", 20 | 100 21 | ] 22 | } 23 | } -------------------------------------------------------------------------------- /.git-blame-ignore-revs: -------------------------------------------------------------------------------- 1 | # Replace prettier and eslint with Biome 2 | c8f64df7e1f69ee8c0973baf7fe3acfea4403bcf -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base", 4 | "group:allNonMajor", 5 | "schedule:monthly", 6 | ":assignAndReview(jorgebodega)", 7 | ":automergeDisabled", 8 | ":semanticCommits", 9 | ":semanticCommitScopeDisabled", 10 | ":separatePatchReleases", 11 | ":timezone(UTC+1)" 12 | ], 13 | "baseBranches": ["next"], 14 | "packageRules": [ 15 | { 16 | "matchPackagePatterns": ["typeorm"], 17 | "groupName": "typeorm", 18 | "automerge": false 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /.github/workflows/commitlint.yml: -------------------------------------------------------------------------------- 1 | name: Commitlint 2 | on: 3 | [push, pull_request] 4 | jobs: 5 | commitlint: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v4 9 | with: 10 | fetch-depth: 0 11 | - uses: pnpm/action-setup@v2 12 | with: 13 | version: latest 14 | - uses: actions/setup-node@v4 15 | with: 16 | node-version-file: '.node-version' 17 | cache: pnpm 18 | - run: pnpm install --frozen-lockfile 19 | - uses: wagoid/commitlint-github-action@v6 20 | with: 21 | configFile: .commitlintrc 22 | -------------------------------------------------------------------------------- /.github/workflows/coverage.yml: -------------------------------------------------------------------------------- 1 | name: Generate coverage on CI 2 | on: 3 | pull_request: 4 | branches: [ master, next ] 5 | push: 6 | branches: [ master, next ] 7 | jobs: 8 | coverage: 9 | name: Test coverage 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | - uses: pnpm/action-setup@v2 14 | with: 15 | version: latest 16 | - uses: actions/setup-node@v4 17 | with: 18 | node-version-file: '.node-version' 19 | cache: pnpm 20 | - run: pnpm install --frozen-lockfile 21 | - run: pnpm test:cov 22 | - uses: coverallsapp/github-action@master 23 | with: 24 | github-token: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/quality.yml: -------------------------------------------------------------------------------- 1 | name: Quality checks 2 | on: 3 | push: 4 | branches: [ master, next ] 5 | pull_request: 6 | branches: [ master, next ] 7 | jobs: 8 | format: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v4 12 | - uses: pnpm/action-setup@v2 13 | with: 14 | version: latest 15 | - uses: actions/setup-node@v4 16 | with: 17 | node-version-file: '.node-version' 18 | cache: pnpm 19 | - run: pnpm install --frozen-lockfile 20 | - run: pnpm format:ci 21 | lint: 22 | runs-on: ubuntu-latest 23 | steps: 24 | - uses: actions/checkout@v4 25 | - uses: pnpm/action-setup@v2 26 | with: 27 | version: latest 28 | - uses: actions/setup-node@v4 29 | with: 30 | node-version-file: '.node-version' 31 | cache: pnpm 32 | - run: pnpm install --frozen-lockfile 33 | - run: pnpm lint:ci 34 | typecheck: 35 | runs-on: ubuntu-latest 36 | steps: 37 | - uses: actions/checkout@v4 38 | - uses: pnpm/action-setup@v2 39 | with: 40 | version: latest 41 | - uses: actions/setup-node@v4 42 | with: 43 | node-version-file: '.node-version' 44 | cache: pnpm 45 | - run: pnpm install --frozen-lockfile 46 | - run: pnpm typecheck 47 | test: 48 | runs-on: ubuntu-latest 49 | steps: 50 | - uses: actions/checkout@v4 51 | - uses: pnpm/action-setup@v2 52 | with: 53 | version: latest 54 | - uses: actions/setup-node@v4 55 | with: 56 | node-version-file: '.node-version' 57 | cache: pnpm 58 | - run: pnpm install --frozen-lockfile 59 | - run: pnpm test:ci -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | dry-run: 6 | description: 'Check release instead of publishing' 7 | default: "false" 8 | required: false 9 | jobs: 10 | release: 11 | name: Generate a new release 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: pnpm/action-setup@v2 16 | with: 17 | version: latest 18 | - uses: actions/setup-node@v4 19 | with: 20 | node-version-file: '.node-version' 21 | cache: pnpm 22 | - run: pnpm install --frozen-lockfile 23 | - run: pnpm build 24 | - uses: cycjimmy/semantic-release-action@v3 25 | env: 26 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 27 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 28 | with: 29 | semantic_version: 19 30 | dry_run: ${{ github.event.inputs.dry-run }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### generated stuff ### 2 | coverage 3 | dist 4 | test.db 5 | .vscode 6 | 7 | ### node ### 8 | node_modules 9 | 10 | # IDE 11 | .vscode/* 12 | !.vscode/launch.json 13 | -------------------------------------------------------------------------------- /.node-version: -------------------------------------------------------------------------------- 1 | lts/iron -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .commitlintrc 2 | .github 3 | .node-version 4 | .releaserc.json 5 | .sgcrc 6 | .vscode 7 | biome.json 8 | examples 9 | jest.config.ts 10 | src 11 | test 12 | test.db 13 | tsconfig.build.json 14 | tsconfig.json -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | access=public 2 | -------------------------------------------------------------------------------- /.releaserc.json: -------------------------------------------------------------------------------- 1 | { 2 | "branches": ["main", { "name": "next", "channel": "next", "prerelease": true }], 3 | "plugins": [ 4 | [ 5 | "@semantic-release/commit-analyzer", 6 | { 7 | "releaseRules": [{ "type": "chore", "release": "patch" }], 8 | "parserOpts": { 9 | "noteKeywords": ["BREAKING CHANGE", "BREAKING CHANGES"] 10 | } 11 | } 12 | ], 13 | [ 14 | "@semantic-release/release-notes-generator", 15 | { 16 | "parserOpts": { 17 | "noteKeywords": ["BREAKING CHANGE", "BREAKING CHANGES"] 18 | } 19 | } 20 | ], 21 | [ 22 | "@semantic-release/changelog", 23 | { 24 | "changelogFile": "CHANGELOG.md" 25 | } 26 | ], 27 | "@semantic-release/npm", 28 | [ 29 | "@semantic-release/git", 30 | { 31 | "assets": ["package.json", "pnpm-lock.yaml", "CHANGELOG.md"], 32 | "message": "chore: release ${nextRelease.version}\n\n${nextRelease.notes}" 33 | } 34 | ], 35 | ["@semantic-release/github", { "successComment": false, "failComment": false, "failTitle": false }] 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /.sgcrc: -------------------------------------------------------------------------------- 1 | { 2 | "lowercaseTypes": true 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["biomejs.biome"] 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[javascript]": { 3 | "editor.defaultFormatter": "biomejs.biome", 4 | "editor.formatOnSave": true 5 | }, 6 | "[json]": { 7 | "editor.defaultFormatter": "biomejs.biome", 8 | "editor.formatOnSave": true 9 | }, 10 | "[jsonc]": { 11 | "editor.defaultFormatter": "biomejs.biome", 12 | "editor.formatOnSave": true 13 | }, 14 | "[typescript]": { 15 | "editor.defaultFormatter": "biomejs.biome", 16 | "editor.formatOnSave": true 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # [2.1.0](https://github.com/jorgebodega/typeorm-factory/compare/v2.0.0...v2.1.0) (2024-10-04) 2 | 3 | 4 | ### Features 5 | 6 | * add biome instead of prettier and eslint ([#178](https://github.com/jorgebodega/typeorm-factory/issues/178)) ([c8f64df](https://github.com/jorgebodega/typeorm-factory/commit/c8f64df7e1f69ee8c0973baf7fe3acfea4403bcf)) 7 | 8 | # [2.1.0-next.3](https://github.com/jorgebodega/typeorm-factory/compare/v2.1.0-next.2...v2.1.0-next.3) (2024-10-04) 9 | 10 | # [2.1.0-next.2](https://github.com/jorgebodega/typeorm-factory/compare/v2.1.0-next.1...v2.1.0-next.2) (2024-09-25) 11 | 12 | # [2.1.0-next.1](https://github.com/jorgebodega/typeorm-factory/compare/v2.0.0...v2.1.0-next.1) (2024-06-22) 13 | 14 | 15 | ### Features 16 | 17 | * add biome instead of prettier and eslint ([#178](https://github.com/jorgebodega/typeorm-factory/issues/178)) ([c8f64df](https://github.com/jorgebodega/typeorm-factory/commit/c8f64df7e1f69ee8c0973baf7fe3acfea4403bcf)) 18 | 19 | # [2.0.0](https://github.com/jorgebodega/typeorm-factory/compare/v1.4.0...v2.0.0) (2024-03-18) 20 | 21 | 22 | ### chore 23 | 24 | * bump minimum version for node use ([7920e1d](https://github.com/jorgebodega/typeorm-factory/commit/7920e1d097a8c652fc832542956a40398182d5a8)) 25 | 26 | 27 | ### Features 28 | 29 | * allow overriding entity manager creation to allow end user managing external transactions ([#163](https://github.com/jorgebodega/typeorm-factory/issues/163)) ([a249ec2](https://github.com/jorgebodega/typeorm-factory/commit/a249ec22756180e2e4884932489748e07ea3cf68)) 30 | * support for single subfactory array instead of collection ([8974cd4](https://github.com/jorgebodega/typeorm-factory/commit/8974cd47ebc4cf1bf02fa5c7d59b07f7ac04bc87)) 31 | * update typescript ([263b408](https://github.com/jorgebodega/typeorm-factory/commit/263b4085a8c50aa30c65ace2653ed8c9e48d4910)) 32 | * upgrade node version ([f990a50](https://github.com/jorgebodega/typeorm-factory/commit/f990a50feb98f83bb9161207f96b75f8529d6ed5)) 33 | 34 | 35 | ### BREAKING CHANGES 36 | 37 | * remove support for node 16 38 | * now Node 14 (lts) and 17 are out-of-life 39 | 40 | # [2.0.0-next.3](https://github.com/jorgebodega/typeorm-factory/compare/v2.0.0-next.2...v2.0.0-next.3) (2024-03-18) 41 | 42 | # [2.0.0-next.2](https://github.com/jorgebodega/typeorm-factory/compare/v2.0.0-next.1...v2.0.0-next.2) (2024-02-28) 43 | 44 | 45 | ### Features 46 | 47 | * allow overriding entity manager creation to allow end user managing external transactions ([#163](https://github.com/jorgebodega/typeorm-factory/issues/163)) ([a249ec2](https://github.com/jorgebodega/typeorm-factory/commit/a249ec22756180e2e4884932489748e07ea3cf68)) 48 | 49 | # [2.0.0-next.1](https://github.com/jorgebodega/typeorm-factory/compare/v1.5.0-next.1...v2.0.0-next.1) (2023-10-30) 50 | 51 | 52 | ### chore 53 | 54 | * bump minimum version for node use ([7920e1d](https://github.com/jorgebodega/typeorm-factory/commit/7920e1d097a8c652fc832542956a40398182d5a8)) 55 | 56 | 57 | ### Features 58 | 59 | * update typescript ([263b408](https://github.com/jorgebodega/typeorm-factory/commit/263b4085a8c50aa30c65ace2653ed8c9e48d4910)) 60 | * upgrade node version ([f990a50](https://github.com/jorgebodega/typeorm-factory/commit/f990a50feb98f83bb9161207f96b75f8529d6ed5)) 61 | 62 | 63 | ### BREAKING CHANGES 64 | 65 | * remove support for node 16 66 | * now Node 14 (lts) and 17 are out-of-life 67 | 68 | # [1.5.0-next.1](https://github.com/jorgebodega/typeorm-factory/compare/v1.4.0...v1.5.0-next.1) (2023-04-26) 69 | 70 | 71 | ### Features 72 | 73 | * support for single subfactory array instead of collection ([8974cd4](https://github.com/jorgebodega/typeorm-factory/commit/8974cd47ebc4cf1bf02fa5c7d59b07f7ac04bc87)) 74 | 75 | # [1.4.0](https://github.com/jorgebodega/typeorm-factory/compare/v1.3.0...v1.4.0) (2023-01-03) 76 | 77 | 78 | ### Bug Fixes 79 | 80 | * types should extends object ([84a081d](https://github.com/jorgebodega/typeorm-factory/commit/84a081dbfbbf9d2b3d140a726312f004f2c558d0)) 81 | 82 | 83 | ### Features 84 | 85 | * support for node 18 ([#53](https://github.com/jorgebodega/typeorm-factory/issues/53)) ([3a7c7f2](https://github.com/jorgebodega/typeorm-factory/commit/3a7c7f2a2723cedadbe77c722fdaa5eae0096929)) 86 | 87 | # [1.4.0-next.1](https://github.com/jorgebodega/typeorm-factory/compare/v1.3.0...v1.4.0-next.1) (2023-01-03) 88 | 89 | 90 | ### Bug Fixes 91 | 92 | * types should extends object ([84a081d](https://github.com/jorgebodega/typeorm-factory/commit/84a081dbfbbf9d2b3d140a726312f004f2c558d0)) 93 | 94 | 95 | ### Features 96 | 97 | * support for node 18 ([#53](https://github.com/jorgebodega/typeorm-factory/issues/53)) ([3a7c7f2](https://github.com/jorgebodega/typeorm-factory/commit/3a7c7f2a2723cedadbe77c722fdaa5eae0096929)) 98 | 99 | # [1.3.0](https://github.com/jorgebodega/typeorm-factory/compare/v1.2.0...v1.3.0) (2022-11-01) 100 | 101 | 102 | ### Bug Fixes 103 | 104 | * types should extends object ([d67e630](https://github.com/jorgebodega/typeorm-factory/commit/d67e6306f347add50f67d762ad985364dc375aec)) 105 | 106 | 107 | ### Features 108 | 109 | * support for node 18 ([#53](https://github.com/jorgebodega/typeorm-factory/issues/53)) ([1dd44ec](https://github.com/jorgebodega/typeorm-factory/commit/1dd44ecdf7873b9acdcf05ce74fb9d2be445903c)) 110 | 111 | # [1.3.0-next.2](https://github.com/jorgebodega/typeorm-factory/compare/v1.3.0-next.1...v1.3.0-next.2) (2022-11-01) 112 | 113 | # [1.3.0-next.1](https://github.com/jorgebodega/typeorm-factory/compare/v1.2.1-next.1...v1.3.0-next.1) (2022-11-01) 114 | 115 | 116 | ### Features 117 | 118 | * support for node 18 ([#53](https://github.com/jorgebodega/typeorm-factory/issues/53)) ([3a7c7f2](https://github.com/jorgebodega/typeorm-factory/commit/3a7c7f2a2723cedadbe77c722fdaa5eae0096929)) 119 | 120 | ## [1.2.1-next.1](https://github.com/jorgebodega/typeorm-factory/compare/v1.2.0...v1.2.1-next.1) (2022-10-15) 121 | 122 | 123 | ### Bug Fixes 124 | 125 | * types should extends object ([84a081d](https://github.com/jorgebodega/typeorm-factory/commit/84a081dbfbbf9d2b3d140a726312f004f2c558d0)) 126 | 127 | # [1.2.0](https://github.com/jorgebodega/typeorm-factory/compare/v1.1.3...v1.2.0) (2022-08-19) 128 | 129 | 130 | ### Bug Fixes 131 | 132 | * semantic release config ([510e95a](https://github.com/jorgebodega/typeorm-factory/commit/510e95a03801816640c8789325eabe7ba9d701ab)) 133 | 134 | 135 | ### Features 136 | 137 | * allow pass instance to subfactory ([4bd8b8d](https://github.com/jorgebodega/typeorm-factory/commit/4bd8b8dc54f9842e37227ab4cbbe7e884b88cf1d)) 138 | 139 | # [1.2.0-next.1](https://github.com/jorgebodega/typeorm-factory/compare/v1.1.3...v1.2.0-next.1) (2022-08-19) 140 | 141 | 142 | ### Bug Fixes 143 | 144 | * semantic release config ([510e95a](https://github.com/jorgebodega/typeorm-factory/commit/510e95a03801816640c8789325eabe7ba9d701ab)) 145 | 146 | 147 | ### Features 148 | 149 | * allow pass instance to subfactory ([4bd8b8d](https://github.com/jorgebodega/typeorm-factory/commit/4bd8b8dc54f9842e37227ab4cbbe7e884b88cf1d)) 150 | 151 | ## [1.1.3](https://github.com/jorgebodega/typeorm-factory/compare/v1.1.2...v1.1.3) (2022-08-17) 152 | 153 | 154 | ### Bug Fixes 155 | 156 | * semantic release config ([3f8ef42](https://github.com/jorgebodega/typeorm-factory/commit/3f8ef42f0c93c9dc2f615a0b6cf245b18ecee63e)) 157 | 158 | ## [1.1.2](https://github.com/jorgebodega/typeorm-factory/compare/v1.1.1...v1.1.2) (2022-07-29) 159 | 160 | 161 | ### Bug Fixes 162 | 163 | * add keywords to package definition ([8e5d7cc](https://github.com/jorgebodega/typeorm-factory/commit/8e5d7cc593cb4da03948c09039fbf42c809a861f)) 164 | * add missing declaration types for typescript ([41c5950](https://github.com/jorgebodega/typeorm-factory/commit/41c5950fdc1099506eb9af9fe3e27c08e11534f0)) 165 | 166 | ## [1.1.2-next.1](https://github.com/jorgebodega/typeorm-factory/compare/v1.1.1...v1.1.2-next.1) (2022-07-29) 167 | 168 | 169 | ### Bug Fixes 170 | 171 | * add keywords to package definition ([8e5d7cc](https://github.com/jorgebodega/typeorm-factory/commit/8e5d7cc593cb4da03948c09039fbf42c809a861f)) 172 | * add missing declaration types for typescript ([41c5950](https://github.com/jorgebodega/typeorm-factory/commit/41c5950fdc1099506eb9af9fe3e27c08e11534f0)) 173 | 174 | ## [1.1.1](https://github.com/jorgebodega/typeorm-factory/compare/v1.1.0...v1.1.1) (2022-07-28) 175 | 176 | 177 | ### Bug Fixes 178 | 179 | * add missing declaration types for typescript ([7dd62fb](https://github.com/jorgebodega/typeorm-factory/commit/7dd62fb59e41be3b1609f645b9f8a2554b1d8bac)) 180 | 181 | ## [1.1.1-next.1](https://github.com/jorgebodega/typeorm-factory/compare/v1.1.0...v1.1.1-next.1) (2022-07-28) 182 | 183 | 184 | ### Bug Fixes 185 | 186 | * add missing declaration types for typescript ([41c5950](https://github.com/jorgebodega/typeorm-factory/commit/41c5950fdc1099506eb9af9fe3e27c08e11534f0)) 187 | 188 | # [1.1.0](https://github.com/jorgebodega/typeorm-factory/compare/v1.0.0...v1.1.0) (2022-07-28) 189 | 190 | 191 | ### Features 192 | 193 | * add specific subfactories per single and array fields ([ab6ce53](https://github.com/jorgebodega/typeorm-factory/commit/ab6ce537211620cfac47944d5e8c05468394fea3)) 194 | * remove global datasource in favour of single one ([10094e9](https://github.com/jorgebodega/typeorm-factory/commit/10094e92d4ef8812d01fa107eed609162ad34e74)) 195 | * rename instance attributes to be more accurate ([60d416b](https://github.com/jorgebodega/typeorm-factory/commit/60d416b527e19566f8fee1583145732b14b5908c)) 196 | 197 | # [1.1.0-next.1](https://github.com/jorgebodega/typeorm-factory/compare/v1.0.0...v1.1.0-next.1) (2022-07-28) 198 | 199 | 200 | ### Features 201 | 202 | * add specific subfactories per single and array fields ([ab6ce53](https://github.com/jorgebodega/typeorm-factory/commit/ab6ce537211620cfac47944d5e8c05468394fea3)) 203 | * remove global datasource in favour of single one ([10094e9](https://github.com/jorgebodega/typeorm-factory/commit/10094e92d4ef8812d01fa107eed609162ad34e74)) 204 | * rename instance attributes to be more accurate ([60d416b](https://github.com/jorgebodega/typeorm-factory/commit/60d416b527e19566f8fee1583145732b14b5908c)) 205 | 206 | # 1.0.0 (2022-07-28) 207 | 208 | # 1.0.0-next.1 (2022-07-26) 209 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Jorge Bodega Fernanz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

TypeORM Factory

2 | 3 |

4 | NPM 5 | 6 | Coveralls main branch 7 | 8 |

9 | 10 |

11 | A delightful way to use factories in your code.
12 | Inspired by Factory Boy in Python, MikroORM seeding and the repositories from pleerock
13 |

14 | 15 |

16 | Made with ❤️ by Jorge Bodega and contributors 17 |

18 | 19 |
20 | 21 | # Contents 22 | 23 | - [Installation](#installation) 24 | - [Introduction](#introduction) 25 | - [Factory](#factory-1) 26 | - [`make` & `makeMany`](#make--makemany) 27 | - [`create` & `createMany`](#create--createmany) 28 | - [`attrs`](#attrs) 29 | - [Simple value](#simple-value) 30 | - [Function](#function) 31 | - [InstanceAttribute](#instanceattribute) 32 | - [Subfactory](#subfactory) 33 | - [Examples](#examples) 34 | - [Single entity](examples/single-entity/README.md) 35 | - [1-to-1 related](examples/1-to-1-related/README.md) 36 | - [1-to-1 nullable related](examples/1-to-1-nullable-related/README.md) 37 | - [1-to-1 chained related](examples/1-to-1-chained-related/README.md) 38 | - [1-to-N related](examples/1-to-N-related/README.md) 39 | - [N-to-M related](examples/N-to-M-related/README.md) 40 | 41 | # Installation 42 | 43 | Before using this TypeORM extension please read the [TypeORM Getting Started](https://typeorm.io/#/) documentation. This explains how to setup a TypeORM project. 44 | 45 | After that, install the extension. Add development flag if you are not using factories in production code. 46 | 47 | ```bash 48 | npm i [-D] @jorgebodega/typeorm-factory 49 | yarn add [-D] @jorgebodega/typeorm-factory 50 | pnpm add [-D] @jorgebodega/typeorm-factory 51 | ``` 52 | 53 | # Introduction 54 | 55 | Isn't it exhausting to create some sample data for your database, well this time is over! 56 | 57 | How does it work? Just create a entity factory. 58 | 59 | ### Entity 60 | 61 | ```ts 62 | @Entity() 63 | export class Pet { 64 | @PrimaryGeneratedColumn('increment') 65 | id!: string 66 | 67 | @Column() 68 | name!: string 69 | 70 | @ManyToOne(() => User, (user) => user.pets) 71 | @JoinColumn({ name: 'owner_id' }) 72 | owner!: User 73 | } 74 | ``` 75 | 76 | ### Factory 77 | 78 | ```ts 79 | export class PetFactory extends Factory { 80 | protected entity = Pet 81 | protected dataSource = dataSource 82 | protected attrs(): FactorizedAttrs { 83 | return { 84 | name: faker.animal.insect(), 85 | owner: new LazyInstanceAttribute((instance) => new SingleSubfactory(UserFactory, { pets: [instance] })), 86 | } 87 | } 88 | } 89 | ``` 90 | 91 | # Factory 92 | 93 | Factory is how we provide a way to simplify entities creation, implementing a [factory creational pattern](https://refactoring.guru/design-patterns/factory-method). It is defined as an abstract class with generic typing, so you have to extend over it. 94 | 95 | ```ts 96 | class UserFactory extends Factory { 97 | protected entity = User 98 | protected dataSource = dataSource // Imported datasource 99 | protected attrs(): FactorizedAttrs = { 100 | ... 101 | } 102 | } 103 | ``` 104 | 105 | ## `make` & `makeMany` 106 | 107 | Make and makeMany executes the factory functions and return a new instance of the given entity. The instance is filled with the generated values from the factory function, but not saved in the database. 108 | 109 | - **overrideParams** - Override some of the attributes of the entity. 110 | 111 | ```ts 112 | make(overrideParams: Partial> = {}): Promise 113 | makeMany(amount: number, overrideParams: Partial> = {}): Promise 114 | ``` 115 | 116 | ```ts 117 | new UserFactory().make() 118 | new UserFactory().makeMany(10) 119 | 120 | // override the email 121 | new UserFactory().make({ email: 'other@mail.com' }) 122 | new UserFactory().makeMany(10, { email: 'other@mail.com' }) 123 | ``` 124 | 125 | ## `create` & `createMany` 126 | 127 | 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 using TypeORM entity manager. 128 | 129 | - **overrideParams** - Override some of the attributes of the entity. 130 | - **saveOptions** - [Save options](https://github.com/typeorm/typeorm/blob/master/src/repository/SaveOptions.ts) from TypeORM 131 | 132 | ```ts 133 | create(overrideParams: Partial> = {}, saveOptions?: SaveOptions): Promise 134 | createMany(amount: number, overrideParams: Partial> = {}, saveOptions?: SaveOptions): Promise 135 | ``` 136 | 137 | ```ts 138 | new UserFactory().create() 139 | new UserFactory().createMany(10) 140 | 141 | // override the email 142 | new UserFactory().create({ email: 'other@mail.com' }) 143 | new UserFactory().createMany(10, { email: 'other@mail.com' }) 144 | 145 | // using save options 146 | new UserFactory().create({ email: 'other@mail.com' }, { listeners: false }) 147 | new UserFactory().createMany(10, { email: 'other@mail.com' }, { listeners: false }) 148 | ``` 149 | 150 | ## `attrs` 151 | 152 | Attributes objects are superset from the original entity attributes. 153 | 154 | ```ts 155 | protected attrs: FactorizedAttrs = { 156 | name: faker.person.firstName(), 157 | lastName: async () => faker.person.lastName(), 158 | email: new InstanceAttribute((instance) => 159 | [instance.name.toLowerCase(), instance.lastName.toLowerCase(), '@email.com'].join(''), 160 | ), 161 | country: new Subfactory(CountryFactory), 162 | } 163 | ``` 164 | 165 | Those factorized attributes resolves to the value of the original attribute, and could be one of the following types: 166 | 167 | - [Simple value](#simple-value) 168 | - [Function](#function) 169 | - [InstanceAttribute](#instanceattribute) 170 | - [Subfactory](#subfactory) 171 | 172 | ### Simple value 173 | 174 | Nothing special, just a value with same type. 175 | 176 | ```ts 177 | protected attrs(): FactorizedAttrs = { 178 | return { 179 | name: faker.person.firstName(), 180 | } 181 | } 182 | ``` 183 | 184 | ### Function 185 | 186 | Function that could be sync or async, and return a value of the same type. 187 | 188 | ```ts 189 | protected attrs: FactorizedAttrs = { 190 | return { 191 | lastName: async () => faker.person.lastName(), 192 | } 193 | } 194 | ``` 195 | 196 | ### InstanceAttribute 197 | 198 | Class with a function that receive the current instance and returns a value of the same type. It is ideal for attributes that could depend on some others to be computed. 199 | 200 | ```ts 201 | protected attrs: FactorizedAttrs = { 202 | return { 203 | ..., 204 | email: new EagerInstanceAttribute((instance) => 205 | [instance.name.toLowerCase(), instance.lastName.toLowerCase(), '@email.com'].join(''), 206 | ), 207 | } 208 | } 209 | ``` 210 | 211 | In this simple case, if `name` or `lastName` override the value in any way, the `email` attribute will be affected too. 212 | 213 | There are two types of `InstanceAttribute`: 214 | 215 | - `EagerInstanceAttribute`: Executed after creation of the entity and before persisting it, so database id will be undefined. 216 | - `LazyInstanceAttribute`: Executed after creation of the entity and after persisting it. 217 | 218 | Just remember that, if you use `make` or `makeMany`, the only difference between `EagerInstanceAttribute` and `LazyInstanceAttribute` is that `LazyInstanceAttribute` will be processed the last. 219 | 220 | ### Subfactory 221 | 222 | Subfactories are just a wrapper of another factory. This could help to avoid explicit operations that could lead to unexpected results over that factory, like 223 | 224 | ```ts 225 | protected attrs: FactorizedAttrs = { 226 | country: async () => new CountryFactory().create({ 227 | name: faker.address.country(), 228 | }), 229 | } 230 | ``` 231 | 232 | instead of the same with 233 | 234 | ```ts 235 | protected attrs: FactorizedAttrs = { 236 | country: new SingleSubfactory(CountryFactory, { 237 | name: faker.address.country(), 238 | }), 239 | } 240 | ``` 241 | 242 | Subfactories could be created in two ways, allowing you to specify only the class or passing the instance already created. This could be useful if you have some specific class-related code in your factories: 243 | 244 | ```ts 245 | protected attrs: FactorizedAttrs = { 246 | country: new SingleSubfactory(CountryFactory, { 247 | name: faker.address.country(), 248 | }), 249 | // or 250 | country: new SingleSubfactory(new CountryFactory(), { 251 | name: faker.address.country(), 252 | }), 253 | } 254 | ``` 255 | 256 | Subfactory just execute the same kind of operation (`make` or `create`) over the factory. There are two types of `Subfactory`: 257 | 258 | - `SingleSubfactory`: Execute `make` or `create` to return a single element. 259 | - `CollectionSubfactory`: Execute `makeMany` or `createMany` to return an array of elements. 260 | 261 | A `CollectionSubfactory` is equivalent now to an array of `SingleSubfactory`, so this two statements produce the same result. 262 | 263 | ```ts 264 | protected attrs: FactorizedAttrs = { 265 | pets: new CollectionSubfactory(PetFactory, 2, ...) 266 | // or 267 | pets: [ 268 | new SingleSubfactory(PetFactory, ...), 269 | new SingleSubfactory(PetFactory, ...), 270 | ], 271 | } 272 | ``` 273 | 274 | # Examples 275 | 276 | Some basic examples of how to use the library could be found on the `examples` folder. 277 | 278 | - [Single entity](examples/single-entity/README.md) 279 | - [1-to-1 related](examples/1-to-1-related/README.md) 280 | - [1-to-1 nullable related](examples/1-to-1-nullable-related/README.md) 281 | - [1-to-1 chained related](examples/1-to-1-chained-related/README.md) 282 | - [1-to-N related](examples/1-to-N-related/README.md) 283 | - [N-to-M related](examples/N-to-M-related/README.md) -------------------------------------------------------------------------------- /biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.8.2/schema.json", 3 | "formatter": { 4 | "ignore": ["**/dist/**"], 5 | "lineWidth": 120 6 | }, 7 | "linter": { 8 | "enabled": true, 9 | "ignore": ["**/dist/**"], 10 | "rules": { 11 | "recommended": true 12 | } 13 | }, 14 | "organizeImports": { 15 | "enabled": true 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/1-to-1-chained-related/README.md: -------------------------------------------------------------------------------- 1 | ## Three entities with a 1-to-1 relationship 2 | 3 | This example shows how to create three entities with a 1-to-1 relationship. The `User` entity has a `Pet` entity, which is also related to `Refuge`. 4 | 5 | ```typescript 6 | // factories/UserFactory.ts 7 | export class UserFactory extends Factory { 8 | ... 9 | protected attrs(): FactorizedAttrs { 10 | return { 11 | ... 12 | pet: new LazyInstanceAttribute((instance) => new SingleSubfactory(PetFactory, { owner: instance })), 13 | } 14 | } 15 | } 16 | 17 | // factories/PetFactory.ts 18 | export class PetFactory extends Factory { 19 | ... 20 | protected attrs(): FactorizedAttrs { 21 | return { 22 | ... 23 | owner: new EagerInstanceAttribute((instance) => new SingleSubfactory(UserFactory, { pet: instance })), 24 | refuge: new LazyInstanceAttribute((instance) => new SingleSubfactory(RefugeFactory, { pet: instance })), 25 | } 26 | } 27 | } 28 | 29 | // factories/RefugeFactory.ts 30 | export class RefugeFactory extends Factory { 31 | ... 32 | protected attrs(): FactorizedAttrs { 33 | return { 34 | ... 35 | pet: new EagerInstanceAttribute((instance) => new SingleSubfactory(PetFactory, { refuge: instance })), 36 | } 37 | } 38 | } 39 | ``` 40 | 41 | The `Pet` and `Refuge` entities are the ones that have the relation column, so cannot be created **before** the `User` entity. That's why the `UserFactory` has a `LazyInstanceAttribute` for the `pet` attribute, which will create the `Pet` entity **after** the `User` entity is created. Similar workflow for the `Refuge` entity, that needs to be created **after** the `Pet`. Some more examples could be found on both test files. 42 | -------------------------------------------------------------------------------- /examples/1-to-1-chained-related/dataSource.ts: -------------------------------------------------------------------------------- 1 | import { DataSource } from "typeorm"; 2 | 3 | export const dataSource = new DataSource({ 4 | type: "sqlite", 5 | database: ":memory:", 6 | entities: [`${__dirname}/**/*.entity.ts`], 7 | }); 8 | -------------------------------------------------------------------------------- /examples/1-to-1-chained-related/entities/Pet.entity.ts: -------------------------------------------------------------------------------- 1 | import { Entity, PrimaryGeneratedColumn, Column, JoinColumn, OneToOne } from "typeorm"; 2 | import { Refuge } from "./Refuge.entity"; 3 | import { User } from "./User.entity"; 4 | 5 | @Entity() 6 | export class Pet { 7 | @PrimaryGeneratedColumn("increment") 8 | id!: string; 9 | 10 | @Column() 11 | name!: string; 12 | 13 | @OneToOne( 14 | () => User, 15 | (user) => user.pet, 16 | { nullable: false }, 17 | ) 18 | @JoinColumn({ name: "owner_id" }) 19 | owner!: User; 20 | 21 | @OneToOne( 22 | () => Refuge, 23 | (refuge) => refuge.pet, 24 | { nullable: false }, 25 | ) 26 | refuge!: Refuge; 27 | } 28 | -------------------------------------------------------------------------------- /examples/1-to-1-chained-related/entities/Refuge.entity.ts: -------------------------------------------------------------------------------- 1 | import { Entity, PrimaryGeneratedColumn, Column, JoinColumn, OneToOne } from "typeorm"; 2 | import { Pet } from "./Pet.entity"; 3 | 4 | @Entity() 5 | export class Refuge { 6 | @PrimaryGeneratedColumn("increment") 7 | id!: string; 8 | 9 | @Column() 10 | name!: string; 11 | 12 | @OneToOne( 13 | () => Pet, 14 | (pet) => pet.refuge, 15 | { nullable: false }, 16 | ) 17 | @JoinColumn({ name: "pet_id" }) 18 | pet!: Pet; 19 | } 20 | -------------------------------------------------------------------------------- /examples/1-to-1-chained-related/entities/User.entity.ts: -------------------------------------------------------------------------------- 1 | import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn, OneToOne } from "typeorm"; 2 | import { Pet } from "./Pet.entity"; 3 | 4 | @Entity() 5 | export class User { 6 | @PrimaryGeneratedColumn("increment") 7 | id!: number; 8 | 9 | @CreateDateColumn({ name: "created_at" }) 10 | createdAt!: Date; 11 | 12 | @UpdateDateColumn({ name: "updated_at" }) 13 | updatedAt!: Date; 14 | 15 | @Column() 16 | name!: string; 17 | 18 | @Column({ name: "last_name" }) 19 | lastName!: string; 20 | 21 | @OneToOne( 22 | () => Pet, 23 | (pet) => pet.owner, 24 | { nullable: false }, 25 | ) 26 | pet!: Pet; 27 | } 28 | -------------------------------------------------------------------------------- /examples/1-to-1-chained-related/factories/Pet.factory.ts: -------------------------------------------------------------------------------- 1 | import { faker } from "@faker-js/faker"; 2 | import { 3 | EagerInstanceAttribute, 4 | type FactorizedAttrs, 5 | Factory, 6 | LazyInstanceAttribute, 7 | SingleSubfactory, 8 | } from "../../../src"; 9 | import { dataSource } from "../dataSource"; 10 | import { Pet } from "../entities/Pet.entity"; 11 | import { RefugeFactory } from "./Refuge.factory"; 12 | import { UserFactory } from "./User.factory"; 13 | 14 | export class PetFactory extends Factory { 15 | protected entity = Pet; 16 | protected dataSource = dataSource; 17 | 18 | protected attrs(): FactorizedAttrs { 19 | return { 20 | name: faker.animal.insect(), 21 | owner: new EagerInstanceAttribute((instance) => new SingleSubfactory(UserFactory, { pet: instance })), 22 | refuge: new LazyInstanceAttribute((instance) => new SingleSubfactory(RefugeFactory, { pet: instance })), 23 | }; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /examples/1-to-1-chained-related/factories/Refuge.factory.ts: -------------------------------------------------------------------------------- 1 | import { faker } from "@faker-js/faker"; 2 | import { EagerInstanceAttribute, type FactorizedAttrs, Factory, SingleSubfactory } from "../../../src"; 3 | import { dataSource } from "../dataSource"; 4 | import { Refuge } from "../entities/Refuge.entity"; 5 | import { PetFactory } from "./Pet.factory"; 6 | 7 | export class RefugeFactory extends Factory { 8 | protected entity = Refuge; 9 | protected dataSource = dataSource; 10 | 11 | protected attrs(): FactorizedAttrs { 12 | return { 13 | name: faker.company.name(), 14 | pet: new EagerInstanceAttribute((instance) => new SingleSubfactory(PetFactory, { refuge: instance })), 15 | }; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/1-to-1-chained-related/factories/User.factory.ts: -------------------------------------------------------------------------------- 1 | import { faker } from "@faker-js/faker"; 2 | import { type FactorizedAttrs, Factory, LazyInstanceAttribute, SingleSubfactory } from "../../../src"; 3 | import { dataSource } from "../dataSource"; 4 | import { User } from "../entities/User.entity"; 5 | import { PetFactory } from "./Pet.factory"; 6 | 7 | export class UserFactory extends Factory { 8 | protected entity = User; 9 | protected dataSource = dataSource; 10 | 11 | protected attrs(): FactorizedAttrs { 12 | return { 13 | name: faker.person.firstName(), 14 | lastName: faker.person.lastName(), 15 | pet: new LazyInstanceAttribute((instance) => new SingleSubfactory(PetFactory, { owner: instance })), 16 | }; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/1-to-1-chained-related/test/PetFactory.test.ts: -------------------------------------------------------------------------------- 1 | import { dataSource } from "../dataSource"; 2 | import { Pet } from "../entities/Pet.entity"; 3 | import { Refuge } from "../entities/Refuge.entity"; 4 | import { User } from "../entities/User.entity"; 5 | import { PetFactory } from "../factories/Pet.factory"; 6 | 7 | describe(PetFactory, () => { 8 | const factory = new PetFactory(); 9 | 10 | describe(PetFactory.prototype.make, () => { 11 | test("Should make a new entity", async () => { 12 | const petMaked = await factory.make(); 13 | 14 | expect(petMaked).toBeInstanceOf(Pet); 15 | expect(petMaked.id).toBeUndefined(); 16 | expect(petMaked.name).toBeDefined(); 17 | 18 | expect(petMaked.refuge).toBeInstanceOf(Refuge); 19 | expect(petMaked.refuge.id).toBeUndefined(); 20 | expect(petMaked.refuge.name).toBeDefined(); 21 | 22 | expect(petMaked.owner).toBeInstanceOf(User); 23 | expect(petMaked.owner.id).toBeUndefined(); 24 | expect(petMaked.owner.name).toBeDefined(); 25 | }); 26 | 27 | test("Should make two entities with different attributes", async () => { 28 | const petMaked1 = await factory.make(); 29 | const petMaked2 = await factory.make(); 30 | 31 | expect(petMaked1).not.toStrictEqual(petMaked2); 32 | }); 33 | }); 34 | 35 | describe(PetFactory.prototype.makeMany, () => { 36 | test("Should make many new entities", async () => { 37 | const count = 2; 38 | const entitiesMaked = await factory.makeMany(count); 39 | 40 | expect(entitiesMaked).toHaveLength(count); 41 | 42 | for (const entity of entitiesMaked) { 43 | expect(entity.id).toBeUndefined(); 44 | 45 | expect(entity.refuge).toBeInstanceOf(Refuge); 46 | expect(entity.refuge.id).toBeUndefined(); 47 | 48 | expect(entity.owner).toBeInstanceOf(User); 49 | expect(entity.owner.id).toBeUndefined(); 50 | } 51 | }); 52 | }); 53 | 54 | describe(PetFactory.prototype.create, () => { 55 | beforeAll(async () => { 56 | await dataSource.initialize(); 57 | }); 58 | 59 | beforeEach(async () => { 60 | await dataSource.synchronize(true); 61 | }); 62 | 63 | afterAll(async () => { 64 | await dataSource.destroy(); 65 | }); 66 | 67 | test("Should create a new entity", async () => { 68 | const petCreated = await factory.create(); 69 | 70 | expect(petCreated).toBeInstanceOf(Pet); 71 | expect(petCreated.id).toBeDefined(); 72 | expect(petCreated.name).toBeDefined(); 73 | 74 | expect(petCreated.refuge).toBeInstanceOf(Refuge); 75 | expect(petCreated.refuge.id).toBeDefined(); 76 | expect(petCreated.refuge.name).toBeDefined(); 77 | expect(petCreated.refuge.pet).toEqual(petCreated); 78 | 79 | expect(petCreated.owner).toBeInstanceOf(User); 80 | expect(petCreated.owner.id).toBeDefined(); 81 | expect(petCreated.owner.name).toBeDefined(); 82 | }); 83 | 84 | test("Should create one entity of each type", async () => { 85 | await factory.create(); 86 | 87 | const [totalUsers, totalPets, totalRefuges] = await Promise.all([ 88 | dataSource.createEntityManager().count(User), 89 | dataSource.createEntityManager().count(Pet), 90 | dataSource.createEntityManager().count(Refuge), 91 | ]); 92 | 93 | expect(totalUsers).toBe(1); 94 | expect(totalPets).toBe(1); 95 | expect(totalRefuges).toBe(1); 96 | }); 97 | 98 | test("Should create two entities with different attributes", async () => { 99 | const petCreated1 = await factory.create(); 100 | const petCreated2 = await factory.create(); 101 | 102 | expect(petCreated1).not.toStrictEqual(petCreated2); 103 | }); 104 | }); 105 | 106 | describe(PetFactory.prototype.createMany, () => { 107 | beforeAll(async () => { 108 | await dataSource.initialize(); 109 | }); 110 | 111 | beforeEach(async () => { 112 | await dataSource.synchronize(true); 113 | }); 114 | 115 | afterAll(async () => { 116 | await dataSource.destroy(); 117 | }); 118 | 119 | test("Should create many new entities", async () => { 120 | const count = 2; 121 | const entitiesCreated = await factory.createMany(count); 122 | 123 | expect(entitiesCreated).toHaveLength(count); 124 | 125 | for (const entity of entitiesCreated) { 126 | expect(entity.id).toBeDefined(); 127 | 128 | expect(entity.refuge).toBeInstanceOf(Refuge); 129 | expect(entity.refuge.id).toBeDefined(); 130 | expect(entity.refuge.pet).toEqual(entity); 131 | 132 | expect(entity.owner).toBeInstanceOf(User); 133 | expect(entity.owner.id).toBeDefined(); 134 | } 135 | }); 136 | 137 | test("Should create many entities of each type", async () => { 138 | const count = 2; 139 | await factory.createMany(2); 140 | 141 | const [totalUsers, totalPets, totalRefuges] = await Promise.all([ 142 | dataSource.createEntityManager().count(User), 143 | dataSource.createEntityManager().count(Pet), 144 | dataSource.createEntityManager().count(Refuge), 145 | ]); 146 | 147 | expect(totalUsers).toBe(count); 148 | expect(totalPets).toBe(count); 149 | expect(totalRefuges).toBe(count); 150 | }); 151 | }); 152 | }); 153 | -------------------------------------------------------------------------------- /examples/1-to-1-chained-related/test/RefugeFactory.test.ts: -------------------------------------------------------------------------------- 1 | import { dataSource } from "../dataSource"; 2 | import { Pet } from "../entities/Pet.entity"; 3 | import { Refuge } from "../entities/Refuge.entity"; 4 | import { User } from "../entities/User.entity"; 5 | import { RefugeFactory } from "../factories/Refuge.factory"; 6 | 7 | describe(RefugeFactory, () => { 8 | const factory = new RefugeFactory(); 9 | 10 | describe(RefugeFactory.prototype.make, () => { 11 | test("Should make a new entity", async () => { 12 | const refugeMaked = await factory.make(); 13 | 14 | expect(refugeMaked).toBeInstanceOf(Refuge); 15 | expect(refugeMaked.id).toBeUndefined(); 16 | expect(refugeMaked.name).toBeDefined(); 17 | 18 | expect(refugeMaked.pet).toBeInstanceOf(Pet); 19 | expect(refugeMaked.pet.id).toBeUndefined(); 20 | expect(refugeMaked.pet.name).toBeDefined(); 21 | 22 | expect(refugeMaked.pet.owner).toBeInstanceOf(User); 23 | expect(refugeMaked.pet.owner.id).toBeUndefined(); 24 | expect(refugeMaked.pet.owner.name).toBeDefined(); 25 | }); 26 | 27 | test("Should make two entities with different attributes", async () => { 28 | const refugeMaked1 = await factory.make(); 29 | const refugeMaked2 = await factory.make(); 30 | 31 | expect(refugeMaked1).not.toStrictEqual(refugeMaked2); 32 | }); 33 | }); 34 | 35 | describe(RefugeFactory.prototype.makeMany, () => { 36 | test("Should make many new entities", async () => { 37 | const count = 2; 38 | const entitiesMaked = await factory.makeMany(count); 39 | 40 | expect(entitiesMaked).toHaveLength(count); 41 | 42 | for (const entity of entitiesMaked) { 43 | expect(entity.id).toBeUndefined(); 44 | 45 | expect(entity.pet).toBeInstanceOf(Pet); 46 | expect(entity.pet.id).toBeUndefined(); 47 | 48 | expect(entity.pet.owner).toBeInstanceOf(User); 49 | expect(entity.pet.owner.id).toBeUndefined(); 50 | } 51 | }); 52 | }); 53 | 54 | describe(RefugeFactory.prototype.create, () => { 55 | beforeAll(async () => { 56 | await dataSource.initialize(); 57 | }); 58 | 59 | beforeEach(async () => { 60 | await dataSource.synchronize(true); 61 | }); 62 | 63 | afterAll(async () => { 64 | await dataSource.destroy(); 65 | }); 66 | 67 | test("Should create a new entity", async () => { 68 | const refugeCreated = await factory.create(); 69 | 70 | expect(refugeCreated).toBeInstanceOf(Refuge); 71 | expect(refugeCreated.id).toBeDefined(); 72 | expect(refugeCreated.name).toBeDefined(); 73 | 74 | expect(refugeCreated.pet).toBeInstanceOf(Pet); 75 | expect(refugeCreated.pet.id).toBeDefined(); 76 | expect(refugeCreated.pet.name).toBeDefined(); 77 | 78 | expect(refugeCreated.pet.owner).toBeInstanceOf(User); 79 | expect(refugeCreated.pet.owner.id).toBeDefined(); 80 | expect(refugeCreated.pet.owner.name).toBeDefined(); 81 | }); 82 | 83 | test("Should create one entity of each type", async () => { 84 | await factory.create(); 85 | 86 | const [totalUsers, totalPets, totalRefuges] = await Promise.all([ 87 | dataSource.createEntityManager().count(User), 88 | dataSource.createEntityManager().count(Pet), 89 | dataSource.createEntityManager().count(Refuge), 90 | ]); 91 | 92 | expect(totalUsers).toBe(1); 93 | expect(totalPets).toBe(1); 94 | expect(totalRefuges).toBe(1); 95 | }); 96 | 97 | test("Should create two entities with different attributes", async () => { 98 | const petCreated1 = await factory.create(); 99 | const petCreated2 = await factory.create(); 100 | 101 | expect(petCreated1).not.toStrictEqual(petCreated2); 102 | }); 103 | }); 104 | 105 | describe(RefugeFactory.prototype.createMany, () => { 106 | beforeAll(async () => { 107 | await dataSource.initialize(); 108 | }); 109 | 110 | beforeEach(async () => { 111 | await dataSource.synchronize(true); 112 | }); 113 | 114 | afterAll(async () => { 115 | await dataSource.destroy(); 116 | }); 117 | 118 | test("Should create many new entities", async () => { 119 | const count = 2; 120 | const entitiesCreated = await factory.createMany(count); 121 | 122 | expect(entitiesCreated).toHaveLength(count); 123 | for (const entity of entitiesCreated) { 124 | expect(entity.id).toBeDefined(); 125 | 126 | expect(entity.pet).toBeInstanceOf(Pet); 127 | expect(entity.pet.id).toBeDefined(); 128 | 129 | expect(entity.pet.owner).toBeInstanceOf(User); 130 | expect(entity.pet.owner.id).toBeDefined(); 131 | } 132 | }); 133 | }); 134 | }); 135 | -------------------------------------------------------------------------------- /examples/1-to-1-chained-related/test/UserFactory.test.ts: -------------------------------------------------------------------------------- 1 | import { dataSource } from "../dataSource"; 2 | import { Pet } from "../entities/Pet.entity"; 3 | import { Refuge } from "../entities/Refuge.entity"; 4 | import { User } from "../entities/User.entity"; 5 | import { UserFactory } from "../factories/User.factory"; 6 | 7 | describe(UserFactory, () => { 8 | const factory = new UserFactory(); 9 | 10 | describe(UserFactory.prototype.make, () => { 11 | test("Should make a new entity", async () => { 12 | const userMaked = await factory.make(); 13 | 14 | expect(userMaked).toBeInstanceOf(User); 15 | expect(userMaked.id).toBeUndefined(); 16 | expect(userMaked.name).toBeDefined(); 17 | expect(userMaked.lastName).toBeDefined(); 18 | 19 | expect(userMaked.pet).toBeInstanceOf(Pet); 20 | expect(userMaked.pet.id).toBeUndefined(); 21 | expect(userMaked.pet.name).toBeDefined(); 22 | expect(userMaked.pet.owner).toEqual(userMaked); 23 | 24 | expect(userMaked.pet.refuge).toBeInstanceOf(Refuge); 25 | expect(userMaked.pet.refuge.id).toBeUndefined(); 26 | expect(userMaked.pet.refuge.name).toBeDefined(); 27 | }); 28 | 29 | test("Should make two entities with different attributes", async () => { 30 | const userMaked1 = await factory.make(); 31 | const userMaked2 = await factory.make(); 32 | 33 | expect(userMaked1).not.toStrictEqual(userMaked2); 34 | }); 35 | }); 36 | 37 | describe(UserFactory.prototype.makeMany, () => { 38 | test("Should make many new entities", async () => { 39 | const count = 2; 40 | const entitiesMaked = await factory.makeMany(count); 41 | 42 | expect(entitiesMaked).toHaveLength(count); 43 | 44 | for (const entity of entitiesMaked) { 45 | expect(entity.id).toBeUndefined(); 46 | 47 | expect(entity.pet).toBeInstanceOf(Pet); 48 | expect(entity.pet.id).toBeUndefined(); 49 | 50 | expect(entity.pet.refuge).toBeInstanceOf(Refuge); 51 | expect(entity.pet.refuge.id).toBeUndefined(); 52 | } 53 | }); 54 | }); 55 | 56 | describe(UserFactory.prototype.create, () => { 57 | beforeAll(async () => { 58 | await dataSource.initialize(); 59 | }); 60 | 61 | beforeEach(async () => { 62 | await dataSource.synchronize(true); 63 | }); 64 | 65 | afterAll(async () => { 66 | await dataSource.destroy(); 67 | }); 68 | 69 | test("Should create a new entity", async () => { 70 | const userCreated = await factory.create(); 71 | 72 | expect(userCreated).toBeInstanceOf(User); 73 | expect(userCreated.id).toBeDefined(); 74 | expect(userCreated.name).toBeDefined(); 75 | expect(userCreated.lastName).toBeDefined(); 76 | 77 | expect(userCreated.pet).toBeInstanceOf(Pet); 78 | expect(userCreated.pet.id).toBeDefined(); 79 | expect(userCreated.pet.name).toBeDefined(); 80 | expect(userCreated.pet.owner).toEqual(userCreated); 81 | 82 | expect(userCreated.pet.refuge).toBeInstanceOf(Refuge); 83 | expect(userCreated.pet.refuge.id).toBeDefined(); 84 | expect(userCreated.pet.refuge.name).toBeDefined(); 85 | }); 86 | 87 | test("Should create one entity of each type", async () => { 88 | await factory.create(); 89 | 90 | const [totalUsers, totalPets, totalRefuges] = await Promise.all([ 91 | dataSource.createEntityManager().count(User), 92 | dataSource.createEntityManager().count(Pet), 93 | dataSource.createEntityManager().count(Refuge), 94 | ]); 95 | 96 | expect(totalUsers).toBe(1); 97 | expect(totalPets).toBe(1); 98 | expect(totalRefuges).toBe(1); 99 | }); 100 | 101 | test("Should create two entities with different attributes", async () => { 102 | const userCreated1 = await factory.create(); 103 | const userCreated2 = await factory.create(); 104 | 105 | expect(userCreated1).not.toStrictEqual(userCreated2); 106 | }); 107 | }); 108 | 109 | describe(UserFactory.prototype.createMany, () => { 110 | beforeAll(async () => { 111 | await dataSource.initialize(); 112 | }); 113 | 114 | beforeEach(async () => { 115 | await dataSource.synchronize(true); 116 | }); 117 | 118 | afterAll(async () => { 119 | await dataSource.destroy(); 120 | }); 121 | 122 | test("Should create many new entities", async () => { 123 | const count = 2; 124 | const entitiesCreated = await factory.createMany(count); 125 | 126 | expect(entitiesCreated).toHaveLength(count); 127 | 128 | for (const entity of entitiesCreated) { 129 | expect(entity.id).toBeDefined(); 130 | 131 | expect(entity.pet).toBeInstanceOf(Pet); 132 | expect(entity.pet.id).toBeDefined(); 133 | expect(entity.pet.owner).toEqual(entity); 134 | 135 | expect(entity.pet.refuge).toBeInstanceOf(Refuge); 136 | expect(entity.pet.refuge.id).toBeDefined(); 137 | } 138 | }); 139 | 140 | test("Should create many entities of each type", async () => { 141 | const count = 2; 142 | await factory.createMany(2); 143 | 144 | const [totalUsers, totalPets, totalRefuges] = await Promise.all([ 145 | dataSource.createEntityManager().count(User), 146 | dataSource.createEntityManager().count(Pet), 147 | dataSource.createEntityManager().count(Refuge), 148 | ]); 149 | 150 | expect(totalUsers).toBe(count); 151 | expect(totalPets).toBe(count); 152 | expect(totalRefuges).toBe(count); 153 | }); 154 | }); 155 | }); 156 | -------------------------------------------------------------------------------- /examples/1-to-1-nullable-related/README.md: -------------------------------------------------------------------------------- 1 | ## Two entities with a 1-to-1 nullable relationship 2 | 3 | This example shows how to create two entities with a 1-to-1 relationship, but one the of sides could be nullable. The `User` entity has a `Pet` entity, which is related to the `User` entity. 4 | 5 | ```typescript 6 | // factories/UserFactory.ts 7 | export class UserFactory extends Factory { 8 | ... 9 | protected attrs(): FactorizedAttrs { 10 | return { 11 | ... 12 | pet: new LazyInstanceAttribute((instance) => new SingleSubfactory(PetFactory, { owner: instance })), // or 13 | pet: undefined, 14 | } 15 | } 16 | } 17 | 18 | // factories/PetFactory.ts 19 | export class PetFactory extends Factory { 20 | ... 21 | protected attrs(): FactorizedAttrs { 22 | return { 23 | ... 24 | owner: new EagerInstanceAttribute((instance) => new SingleSubfactory(UserFactory, { pet: instance })), 25 | } 26 | } 27 | } 28 | ``` 29 | 30 | The `Pet` entity is the one that has the relation column, so it cannot be created **before** the `User` entity. That's why the `UserFactory` has a `LazyInstanceAttribute` for the `pet` attribute, which will create the `Pet` entity **after** the `User` entity is created. Also, the `pet` attribute could be undefined to represent that `User` has no relation. Some more examples could be found on both test files. 31 | -------------------------------------------------------------------------------- /examples/1-to-1-nullable-related/dataSource.ts: -------------------------------------------------------------------------------- 1 | import { DataSource } from "typeorm"; 2 | 3 | export const dataSource = new DataSource({ 4 | type: "sqlite", 5 | database: ":memory:", 6 | entities: [`${__dirname}/**/*.entity.ts`], 7 | }); 8 | -------------------------------------------------------------------------------- /examples/1-to-1-nullable-related/entities/Pet.entity.ts: -------------------------------------------------------------------------------- 1 | import { Entity, PrimaryGeneratedColumn, Column, JoinColumn, OneToOne } from "typeorm"; 2 | import { User } from "./User.entity"; 3 | 4 | @Entity() 5 | export class Pet { 6 | @PrimaryGeneratedColumn("increment") 7 | id!: string; 8 | 9 | @Column() 10 | name!: string; 11 | 12 | @OneToOne( 13 | () => User, 14 | (user) => user.pet, 15 | { nullable: false }, 16 | ) 17 | @JoinColumn({ name: "owner_id" }) 18 | owner!: User; 19 | } 20 | -------------------------------------------------------------------------------- /examples/1-to-1-nullable-related/entities/User.entity.ts: -------------------------------------------------------------------------------- 1 | import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn, OneToOne } from "typeorm"; 2 | import { Pet } from "./Pet.entity"; 3 | 4 | @Entity() 5 | export class User { 6 | @PrimaryGeneratedColumn("increment") 7 | id!: number; 8 | 9 | @CreateDateColumn({ name: "created_at" }) 10 | createdAt!: Date; 11 | 12 | @UpdateDateColumn({ name: "updated_at" }) 13 | updatedAt!: Date; 14 | 15 | @Column() 16 | name!: string; 17 | 18 | @Column({ name: "last_name" }) 19 | lastName!: string; 20 | 21 | @OneToOne( 22 | () => Pet, 23 | (pet) => pet.owner, 24 | { nullable: true }, 25 | ) 26 | pet?: Pet; 27 | } 28 | -------------------------------------------------------------------------------- /examples/1-to-1-nullable-related/factories/Pet.factory.ts: -------------------------------------------------------------------------------- 1 | import { faker } from "@faker-js/faker"; 2 | import { EagerInstanceAttribute, type FactorizedAttrs, Factory, SingleSubfactory } from "../../../src"; 3 | import { dataSource } from "../dataSource"; 4 | import { Pet } from "../entities/Pet.entity"; 5 | import { UserFactory } from "./User.factory"; 6 | 7 | export class PetFactory extends Factory { 8 | protected entity = Pet; 9 | protected dataSource = dataSource; 10 | 11 | protected attrs(): FactorizedAttrs { 12 | return { 13 | name: faker.animal.insect(), 14 | owner: new EagerInstanceAttribute((instance) => new SingleSubfactory(UserFactory, { pet: instance })), 15 | }; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/1-to-1-nullable-related/factories/User.factory.ts: -------------------------------------------------------------------------------- 1 | import { faker } from "@faker-js/faker"; 2 | import { type FactorizedAttrs, Factory } from "../../../src"; 3 | import { dataSource } from "../dataSource"; 4 | import { User } from "../entities/User.entity"; 5 | 6 | export class UserFactory extends Factory { 7 | protected entity = User; 8 | protected dataSource = dataSource; 9 | 10 | protected attrs(): FactorizedAttrs { 11 | return { 12 | name: faker.person.firstName(), 13 | lastName: faker.person.lastName(), 14 | }; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /examples/1-to-1-nullable-related/test/PetFactory.test.ts: -------------------------------------------------------------------------------- 1 | import { dataSource } from "../dataSource"; 2 | import { Pet } from "../entities/Pet.entity"; 3 | import { User } from "../entities/User.entity"; 4 | import { PetFactory } from "../factories/Pet.factory"; 5 | 6 | describe(PetFactory, () => { 7 | const factory = new PetFactory(); 8 | 9 | describe(PetFactory.prototype.make, () => { 10 | test("Should make a new entity", async () => { 11 | const petMaked = await factory.make(); 12 | 13 | expect(petMaked).toBeInstanceOf(Pet); 14 | expect(petMaked.id).toBeUndefined(); 15 | expect(petMaked.name).toBeDefined(); 16 | 17 | expect(petMaked.owner).toBeInstanceOf(User); 18 | expect(petMaked.owner.id).toBeUndefined(); 19 | expect(petMaked.owner.name).toBeDefined(); 20 | }); 21 | 22 | test("Should make two entities with different attributes", async () => { 23 | const petMaked1 = await factory.make(); 24 | const petMaked2 = await factory.make(); 25 | 26 | expect(petMaked1).not.toStrictEqual(petMaked2); 27 | }); 28 | }); 29 | 30 | describe(PetFactory.prototype.makeMany, () => { 31 | test("Should make many new entities", async () => { 32 | const count = 2; 33 | const entitiesMaked = await factory.makeMany(count); 34 | 35 | expect(entitiesMaked).toHaveLength(count); 36 | 37 | for (const entity of entitiesMaked) { 38 | expect(entity.id).toBeUndefined(); 39 | expect(entity.owner).toBeInstanceOf(User); 40 | expect(entity.owner.id).toBeUndefined(); 41 | } 42 | }); 43 | }); 44 | 45 | describe(PetFactory.prototype.create, () => { 46 | beforeAll(async () => { 47 | await dataSource.initialize(); 48 | }); 49 | 50 | beforeEach(async () => { 51 | await dataSource.synchronize(true); 52 | }); 53 | 54 | afterAll(async () => { 55 | await dataSource.destroy(); 56 | }); 57 | 58 | test("Should create a new entity", async () => { 59 | const petCreated = await factory.create(); 60 | 61 | expect(petCreated).toBeInstanceOf(Pet); 62 | expect(petCreated.id).toBeDefined(); 63 | expect(petCreated.name).toBeDefined(); 64 | 65 | expect(petCreated.owner).toBeInstanceOf(User); 66 | expect(petCreated.owner.id).toBeDefined(); 67 | expect(petCreated.owner.name).toBeDefined(); 68 | }); 69 | 70 | test("Should create two entities with different attributes", async () => { 71 | const petCreated1 = await factory.create(); 72 | const petCreated2 = await factory.create(); 73 | 74 | expect(petCreated1).not.toStrictEqual(petCreated2); 75 | }); 76 | }); 77 | 78 | describe(PetFactory.prototype.createMany, () => { 79 | beforeAll(async () => { 80 | await dataSource.initialize(); 81 | }); 82 | 83 | beforeEach(async () => { 84 | await dataSource.synchronize(true); 85 | }); 86 | 87 | afterAll(async () => { 88 | await dataSource.destroy(); 89 | }); 90 | 91 | test("Should create many new entities", async () => { 92 | const count = 2; 93 | const entitiesCreated = await factory.createMany(count); 94 | 95 | expect(entitiesCreated).toHaveLength(count); 96 | 97 | for (const entity of entitiesCreated) { 98 | expect(entity.id).toBeDefined(); 99 | expect(entity.owner).toBeInstanceOf(User); 100 | expect(entity.owner.id).toBeDefined(); 101 | } 102 | }); 103 | }); 104 | }); 105 | -------------------------------------------------------------------------------- /examples/1-to-1-nullable-related/test/UserFactory.test.ts: -------------------------------------------------------------------------------- 1 | import { LazyInstanceAttribute, SingleSubfactory } from "../../../src"; 2 | import { dataSource } from "../dataSource"; 3 | import { Pet } from "../entities/Pet.entity"; 4 | import { User } from "../entities/User.entity"; 5 | import { PetFactory } from "../factories/Pet.factory"; 6 | import { UserFactory } from "../factories/User.factory"; 7 | 8 | describe(UserFactory, () => { 9 | const factory = new UserFactory(); 10 | 11 | describe(UserFactory.prototype.make, () => { 12 | test("Should make a new entity without relation", async () => { 13 | const userMaked = await factory.make(); 14 | 15 | expect(userMaked).toBeInstanceOf(User); 16 | expect(userMaked.id).toBeUndefined(); 17 | expect(userMaked.name).toBeDefined(); 18 | expect(userMaked.lastName).toBeDefined(); 19 | 20 | expect(userMaked.pet).toBeUndefined(); 21 | }); 22 | 23 | test("Should make a new entity with relation", async () => { 24 | const userMaked = await factory.make({ 25 | pet: new LazyInstanceAttribute((instance) => new SingleSubfactory(PetFactory, { owner: instance })), 26 | }); 27 | 28 | expect(userMaked).toBeInstanceOf(User); 29 | expect(userMaked.id).toBeUndefined(); 30 | expect(userMaked.name).toBeDefined(); 31 | expect(userMaked.lastName).toBeDefined(); 32 | 33 | expect(userMaked.pet).toBeDefined(); 34 | expect(userMaked.pet).toBeInstanceOf(Pet); 35 | expect(userMaked.pet?.id).toBeUndefined(); 36 | expect(userMaked.pet?.name).toBeDefined(); 37 | expect(userMaked.pet?.owner).toEqual(userMaked); 38 | }); 39 | 40 | test("Should make two entities with different attributes", async () => { 41 | const userMaked1 = await factory.make(); 42 | const userMaked2 = await factory.make(); 43 | 44 | expect(userMaked1).not.toStrictEqual(userMaked2); 45 | }); 46 | }); 47 | 48 | describe(UserFactory.prototype.makeMany, () => { 49 | test("Should make many new entities without relation", async () => { 50 | const count = 2; 51 | const entitiesMaked = await factory.makeMany(count); 52 | 53 | expect(entitiesMaked).toHaveLength(count); 54 | 55 | for (const entity of entitiesMaked) { 56 | expect(entity.id).toBeUndefined(); 57 | expect(entity.pet).toBeUndefined(); 58 | } 59 | }); 60 | 61 | test("Should make many new entities with relation", async () => { 62 | const count = 2; 63 | const entitiesMaked = await factory.makeMany(count, { 64 | pet: new LazyInstanceAttribute((instance) => new SingleSubfactory(PetFactory, { owner: instance })), 65 | }); 66 | 67 | expect(entitiesMaked).toHaveLength(count); 68 | 69 | for (const entity of entitiesMaked) { 70 | expect(entity.id).toBeUndefined(); 71 | expect(entity.pet).toBeInstanceOf(Pet); 72 | expect(entity.pet?.id).toBeUndefined(); 73 | } 74 | }); 75 | }); 76 | 77 | describe(UserFactory.prototype.create, () => { 78 | beforeAll(async () => { 79 | await dataSource.initialize(); 80 | }); 81 | 82 | beforeEach(async () => { 83 | await dataSource.synchronize(true); 84 | }); 85 | 86 | afterAll(async () => { 87 | await dataSource.destroy(); 88 | }); 89 | 90 | test("Should create a new entity without relation", async () => { 91 | const userCreated = await factory.create(); 92 | 93 | expect(userCreated).toBeInstanceOf(User); 94 | expect(userCreated.id).toBeDefined(); 95 | expect(userCreated.name).toBeDefined(); 96 | expect(userCreated.lastName).toBeDefined(); 97 | 98 | expect(userCreated.pet).toBeUndefined(); 99 | }); 100 | 101 | test("Should create a new entity with relation", async () => { 102 | const userCreated = await factory.create({ 103 | pet: new LazyInstanceAttribute((instance) => new SingleSubfactory(PetFactory, { owner: instance })), 104 | }); 105 | 106 | expect(userCreated).toBeInstanceOf(User); 107 | expect(userCreated.id).toBeDefined(); 108 | expect(userCreated.name).toBeDefined(); 109 | expect(userCreated.lastName).toBeDefined(); 110 | 111 | expect(userCreated.pet).toBeDefined(); 112 | expect(userCreated.pet).toBeInstanceOf(Pet); 113 | expect(userCreated.pet?.id).toBeDefined(); 114 | expect(userCreated.pet?.owner).toEqual(userCreated); 115 | }); 116 | 117 | test("Should create one entity without relation", async () => { 118 | await factory.create(); 119 | 120 | const [totalUsers, totalPets] = await Promise.all([ 121 | dataSource.createEntityManager().count(User), 122 | dataSource.createEntityManager().count(Pet), 123 | ]); 124 | 125 | expect(totalUsers).toBe(1); 126 | expect(totalPets).toBe(0); 127 | }); 128 | 129 | test("Should create one entity of each type with relation", async () => { 130 | await factory.create({ 131 | pet: new LazyInstanceAttribute((instance) => new SingleSubfactory(PetFactory, { owner: instance })), 132 | }); 133 | 134 | const [totalUsers, totalPets] = await Promise.all([ 135 | dataSource.createEntityManager().count(User), 136 | dataSource.createEntityManager().count(Pet), 137 | ]); 138 | 139 | expect(totalUsers).toBe(1); 140 | expect(totalPets).toBe(1); 141 | }); 142 | 143 | test("Should create two entities with different attributes", async () => { 144 | const userCreated1 = await factory.create(); 145 | const userCreated2 = await factory.create(); 146 | 147 | expect(userCreated1).not.toStrictEqual(userCreated2); 148 | }); 149 | }); 150 | 151 | describe(UserFactory.prototype.createMany, () => { 152 | beforeAll(async () => { 153 | await dataSource.initialize(); 154 | }); 155 | 156 | beforeEach(async () => { 157 | await dataSource.synchronize(true); 158 | }); 159 | 160 | afterAll(async () => { 161 | await dataSource.destroy(); 162 | }); 163 | 164 | test("Should create many new entities without relation", async () => { 165 | const count = 2; 166 | const entitiesCreated = await factory.createMany(count); 167 | 168 | expect(entitiesCreated).toHaveLength(count); 169 | 170 | for (const entity of entitiesCreated) { 171 | expect(entity.id).toBeDefined(); 172 | expect(entity.pet).toBeUndefined(); 173 | } 174 | }); 175 | 176 | test("Should create many new entities with relation", async () => { 177 | const count = 2; 178 | const entitiesCreated = await factory.createMany(count, { 179 | pet: new LazyInstanceAttribute((instance) => new SingleSubfactory(PetFactory, { owner: instance })), 180 | }); 181 | 182 | expect(entitiesCreated).toHaveLength(count); 183 | 184 | for (const entity of entitiesCreated) { 185 | expect(entity.id).toBeDefined(); 186 | expect(entity.pet).toBeInstanceOf(Pet); 187 | expect(entity.pet?.id).toBeDefined(); 188 | } 189 | }); 190 | 191 | test("Should create many entities without relation", async () => { 192 | const count = 2; 193 | await factory.createMany(2); 194 | 195 | const [totalUsers, totalPets] = await Promise.all([ 196 | dataSource.createEntityManager().count(User), 197 | dataSource.createEntityManager().count(Pet), 198 | ]); 199 | 200 | expect(totalUsers).toBe(count); 201 | expect(totalPets).toBe(0); 202 | }); 203 | 204 | test("Should create many entities of each type with relations", async () => { 205 | const count = 2; 206 | await factory.createMany(2, { 207 | pet: new LazyInstanceAttribute((instance) => new SingleSubfactory(PetFactory, { owner: instance })), 208 | }); 209 | 210 | const [totalUsers, totalPets] = await Promise.all([ 211 | dataSource.createEntityManager().count(User), 212 | dataSource.createEntityManager().count(Pet), 213 | ]); 214 | 215 | expect(totalUsers).toBe(count); 216 | expect(totalPets).toBe(count); 217 | }); 218 | }); 219 | }); 220 | -------------------------------------------------------------------------------- /examples/1-to-1-related/README.md: -------------------------------------------------------------------------------- 1 | ## Two entities with a 1-to-1 relationship 2 | 3 | This example shows how to create two entities with a 1-to-1 relationship. The `User` entity has a `Pet` entity, which is related to the `User` entity. 4 | 5 | ```typescript 6 | // factories/UserFactory.ts 7 | export class UserFactory extends Factory { 8 | ... 9 | protected attrs(): FactorizedAttrs { 10 | return { 11 | ... 12 | pet: new LazyInstanceAttribute((instance) => new SingleSubfactory(PetFactory, { owner: instance })), 13 | } 14 | } 15 | } 16 | 17 | // factories/PetFactory.ts 18 | export class PetFactory extends Factory { 19 | ... 20 | protected attrs(): FactorizedAttrs { 21 | return { 22 | ... 23 | owner: new EagerInstanceAttribute((instance) => new SingleSubfactory(UserFactory, { pet: instance })), 24 | } 25 | } 26 | } 27 | ``` 28 | 29 | The `Pet` entity is the one that has the relation column, so it cannot be created **before** the `User` entity. That's why the `UserFactory` has a `LazyInstanceAttribute` for the `pet` attribute, which will create the `Pet` entity **after** the `User` entity is created. Some more examples could be found on both test files. 30 | -------------------------------------------------------------------------------- /examples/1-to-1-related/dataSource.ts: -------------------------------------------------------------------------------- 1 | import { DataSource } from "typeorm"; 2 | 3 | export const dataSource = new DataSource({ 4 | type: "sqlite", 5 | database: ":memory:", 6 | entities: [`${__dirname}/**/*.entity.ts`], 7 | }); 8 | -------------------------------------------------------------------------------- /examples/1-to-1-related/entities/Pet.entity.ts: -------------------------------------------------------------------------------- 1 | import { Entity, PrimaryGeneratedColumn, Column, JoinColumn, OneToOne } from "typeorm"; 2 | import { User } from "./User.entity"; 3 | 4 | @Entity() 5 | export class Pet { 6 | @PrimaryGeneratedColumn("increment") 7 | id!: string; 8 | 9 | @Column() 10 | name!: string; 11 | 12 | @OneToOne( 13 | () => User, 14 | (user) => user.pet, 15 | { nullable: false }, 16 | ) 17 | @JoinColumn({ name: "owner_id" }) 18 | owner!: User; 19 | } 20 | -------------------------------------------------------------------------------- /examples/1-to-1-related/entities/User.entity.ts: -------------------------------------------------------------------------------- 1 | import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn, OneToOne } from "typeorm"; 2 | import { Pet } from "./Pet.entity"; 3 | 4 | @Entity() 5 | export class User { 6 | @PrimaryGeneratedColumn("increment") 7 | id!: number; 8 | 9 | @CreateDateColumn({ name: "created_at" }) 10 | createdAt!: Date; 11 | 12 | @UpdateDateColumn({ name: "updated_at" }) 13 | updatedAt!: Date; 14 | 15 | @Column() 16 | name!: string; 17 | 18 | @Column({ name: "last_name" }) 19 | lastName!: string; 20 | 21 | @OneToOne( 22 | () => Pet, 23 | (pet) => pet.owner, 24 | { nullable: false }, 25 | ) 26 | pet!: Pet; 27 | } 28 | -------------------------------------------------------------------------------- /examples/1-to-1-related/factories/Pet.factory.ts: -------------------------------------------------------------------------------- 1 | import { faker } from "@faker-js/faker"; 2 | import { EagerInstanceAttribute, type FactorizedAttrs, Factory, SingleSubfactory } from "../../../src"; 3 | import { dataSource } from "../dataSource"; 4 | import { Pet } from "../entities/Pet.entity"; 5 | import { UserFactory } from "./User.factory"; 6 | 7 | export class PetFactory extends Factory { 8 | protected entity = Pet; 9 | protected dataSource = dataSource; 10 | 11 | protected attrs(): FactorizedAttrs { 12 | return { 13 | name: faker.animal.insect(), 14 | owner: new EagerInstanceAttribute((instance) => new SingleSubfactory(UserFactory, { pet: instance })), 15 | }; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/1-to-1-related/factories/User.factory.ts: -------------------------------------------------------------------------------- 1 | import { faker } from "@faker-js/faker"; 2 | import { type FactorizedAttrs, Factory, LazyInstanceAttribute, SingleSubfactory } from "../../../src"; 3 | import { dataSource } from "../dataSource"; 4 | import { User } from "../entities/User.entity"; 5 | import { PetFactory } from "./Pet.factory"; 6 | 7 | export class UserFactory extends Factory { 8 | protected entity = User; 9 | protected dataSource = dataSource; 10 | 11 | protected attrs(): FactorizedAttrs { 12 | return { 13 | name: faker.person.firstName(), 14 | lastName: faker.person.lastName(), 15 | pet: new LazyInstanceAttribute((instance) => new SingleSubfactory(PetFactory, { owner: instance })), 16 | }; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/1-to-1-related/test/PetFactory.test.ts: -------------------------------------------------------------------------------- 1 | import { dataSource } from "../dataSource"; 2 | import { Pet } from "../entities/Pet.entity"; 3 | import { User } from "../entities/User.entity"; 4 | import { PetFactory } from "../factories/Pet.factory"; 5 | 6 | describe(PetFactory, () => { 7 | const factory = new PetFactory(); 8 | 9 | describe(PetFactory.prototype.make, () => { 10 | test("Should make a new entity", async () => { 11 | const petMaked = await factory.make(); 12 | 13 | expect(petMaked).toBeInstanceOf(Pet); 14 | expect(petMaked.id).toBeUndefined(); 15 | expect(petMaked.name).toBeDefined(); 16 | 17 | expect(petMaked.owner).toBeInstanceOf(User); 18 | expect(petMaked.owner.id).toBeUndefined(); 19 | expect(petMaked.owner.name).toBeDefined(); 20 | }); 21 | 22 | test("Should make two entities with different attributes", async () => { 23 | const petMaked1 = await factory.make(); 24 | const petMaked2 = await factory.make(); 25 | 26 | expect(petMaked1).not.toStrictEqual(petMaked2); 27 | }); 28 | }); 29 | 30 | describe(PetFactory.prototype.makeMany, () => { 31 | test("Should make many new entities", async () => { 32 | const count = 2; 33 | const entitiesMaked = await factory.makeMany(count); 34 | 35 | expect(entitiesMaked).toHaveLength(count); 36 | 37 | for (const entity of entitiesMaked) { 38 | expect(entity.id).toBeUndefined(); 39 | expect(entity.owner).toBeInstanceOf(User); 40 | expect(entity.owner.id).toBeUndefined(); 41 | } 42 | }); 43 | }); 44 | 45 | describe(PetFactory.prototype.create, () => { 46 | beforeAll(async () => { 47 | await dataSource.initialize(); 48 | }); 49 | 50 | beforeEach(async () => { 51 | await dataSource.synchronize(true); 52 | }); 53 | 54 | afterAll(async () => { 55 | await dataSource.destroy(); 56 | }); 57 | 58 | test("Should create a new entity", async () => { 59 | const petCreated = await factory.create(); 60 | 61 | expect(petCreated).toBeInstanceOf(Pet); 62 | expect(petCreated.id).toBeDefined(); 63 | expect(petCreated.name).toBeDefined(); 64 | 65 | expect(petCreated.owner).toBeInstanceOf(User); 66 | expect(petCreated.owner.id).toBeDefined(); 67 | expect(petCreated.owner.name).toBeDefined(); 68 | }); 69 | 70 | test("Should create two entities with different attributes", async () => { 71 | const petCreated1 = await factory.create(); 72 | const petCreated2 = await factory.create(); 73 | 74 | expect(petCreated1).not.toStrictEqual(petCreated2); 75 | }); 76 | }); 77 | 78 | describe(PetFactory.prototype.createMany, () => { 79 | beforeAll(async () => { 80 | await dataSource.initialize(); 81 | }); 82 | 83 | beforeEach(async () => { 84 | await dataSource.synchronize(true); 85 | }); 86 | 87 | afterAll(async () => { 88 | await dataSource.destroy(); 89 | }); 90 | 91 | test("Should create many new entities", async () => { 92 | const count = 2; 93 | const entitiesCreated = await factory.createMany(count); 94 | 95 | expect(entitiesCreated).toHaveLength(count); 96 | 97 | for (const entity of entitiesCreated) { 98 | expect(entity.id).toBeDefined(); 99 | expect(entity.owner).toBeInstanceOf(User); 100 | expect(entity.owner.id).toBeDefined(); 101 | } 102 | }); 103 | }); 104 | }); 105 | -------------------------------------------------------------------------------- /examples/1-to-1-related/test/UserFactory.test.ts: -------------------------------------------------------------------------------- 1 | import { dataSource } from "../dataSource"; 2 | import { Pet } from "../entities/Pet.entity"; 3 | import { User } from "../entities/User.entity"; 4 | import { UserFactory } from "../factories/User.factory"; 5 | 6 | describe(UserFactory, () => { 7 | const factory = new UserFactory(); 8 | 9 | describe(UserFactory.prototype.make, () => { 10 | test("Should make a new entity", async () => { 11 | const userMaked = await factory.make(); 12 | 13 | expect(userMaked).toBeInstanceOf(User); 14 | expect(userMaked.id).toBeUndefined(); 15 | expect(userMaked.name).toBeDefined(); 16 | expect(userMaked.lastName).toBeDefined(); 17 | 18 | expect(userMaked.pet).toBeInstanceOf(Pet); 19 | expect(userMaked.pet.id).toBeUndefined(); 20 | expect(userMaked.pet.name).toBeDefined(); 21 | expect(userMaked.pet.owner).toEqual(userMaked); 22 | }); 23 | 24 | test("Should make two entities with different attributes", async () => { 25 | const userMaked1 = await factory.make(); 26 | const userMaked2 = await factory.make(); 27 | 28 | expect(userMaked1).not.toStrictEqual(userMaked2); 29 | }); 30 | }); 31 | 32 | describe(UserFactory.prototype.makeMany, () => { 33 | test("Should make many new entities", async () => { 34 | const count = 2; 35 | const entitiesMaked = await factory.makeMany(count); 36 | 37 | expect(entitiesMaked).toHaveLength(count); 38 | 39 | for (const entity of entitiesMaked) { 40 | expect(entity.id).toBeUndefined(); 41 | expect(entity.pet).toBeInstanceOf(Pet); 42 | expect(entity.pet.id).toBeUndefined(); 43 | } 44 | }); 45 | }); 46 | 47 | describe(UserFactory.prototype.create, () => { 48 | beforeAll(async () => { 49 | await dataSource.initialize(); 50 | }); 51 | 52 | beforeEach(async () => { 53 | await dataSource.synchronize(true); 54 | }); 55 | 56 | afterAll(async () => { 57 | await dataSource.destroy(); 58 | }); 59 | 60 | test("Should create a new entity", async () => { 61 | const userCreated = await factory.create(); 62 | 63 | expect(userCreated).toBeInstanceOf(User); 64 | expect(userCreated.id).toBeDefined(); 65 | expect(userCreated.name).toBeDefined(); 66 | expect(userCreated.lastName).toBeDefined(); 67 | 68 | expect(userCreated.pet).toBeInstanceOf(Pet); 69 | expect(userCreated.pet.id).toBeDefined(); 70 | expect(userCreated.pet.name).toBeDefined(); 71 | expect(userCreated.pet.owner).toEqual(userCreated); 72 | }); 73 | 74 | test("Should create one entity of each type", async () => { 75 | await factory.create(); 76 | 77 | const [totalUsers, totalPets] = await Promise.all([ 78 | dataSource.createEntityManager().count(User), 79 | dataSource.createEntityManager().count(Pet), 80 | ]); 81 | 82 | expect(totalUsers).toBe(1); 83 | expect(totalPets).toBe(1); 84 | }); 85 | 86 | test("Should create two entities with different attributes", async () => { 87 | const userCreated1 = await factory.create(); 88 | const userCreated2 = await factory.create(); 89 | 90 | expect(userCreated1).not.toStrictEqual(userCreated2); 91 | }); 92 | }); 93 | 94 | describe(UserFactory.prototype.createMany, () => { 95 | beforeAll(async () => { 96 | await dataSource.initialize(); 97 | }); 98 | 99 | beforeEach(async () => { 100 | await dataSource.synchronize(true); 101 | }); 102 | 103 | afterAll(async () => { 104 | await dataSource.destroy(); 105 | }); 106 | 107 | test("Should create many new entities", async () => { 108 | const count = 2; 109 | const entitiesCreated = await factory.createMany(count); 110 | 111 | expect(entitiesCreated).toHaveLength(count); 112 | 113 | for (const entity of entitiesCreated) { 114 | expect(entity.id).toBeDefined(); 115 | expect(entity.pet).toBeInstanceOf(Pet); 116 | expect(entity.pet.id).toBeDefined(); 117 | } 118 | }); 119 | 120 | test("Should create many entities of each type", async () => { 121 | const count = 2; 122 | await factory.createMany(2); 123 | 124 | const [totalUsers, totalPets] = await Promise.all([ 125 | dataSource.createEntityManager().count(User), 126 | dataSource.createEntityManager().count(Pet), 127 | ]); 128 | 129 | expect(totalUsers).toBe(count); 130 | expect(totalPets).toBe(count); 131 | }); 132 | }); 133 | }); 134 | -------------------------------------------------------------------------------- /examples/1-to-N-related/README.md: -------------------------------------------------------------------------------- 1 | ## Two entities with a 1-to-N relationship 2 | 3 | This example shows how to create two entities with a 1-to-N relationship. The `User` entity has many `Pet` entities, which are related to the `User` entity. 4 | 5 | ```typescript 6 | // factories/UserFactory.ts 7 | export class UserFactory extends Factory { 8 | ... 9 | protected attrs(): FactorizedAttrs { 10 | return { 11 | ... 12 | pets: [], // or 13 | pets: new LazyInstanceAttribute((instance) => new CollectionSubfactory(PetFactory, 1, { owner: instance })) 14 | } 15 | } 16 | } 17 | 18 | // factories/PetFactory.ts 19 | export class PetFactory extends Factory { 20 | ... 21 | protected attrs(): FactorizedAttrs { 22 | return { 23 | ... 24 | owner: new EagerInstanceAttribute((instance) => new SingleSubfactory(UserFactory, { pets: [instance] })), 25 | } 26 | } 27 | } 28 | ``` 29 | 30 | The `Pet` entity is the one that has the relation column, so it cannot be created **before** the `User` entity. That's why the `UserFactory` has a `LazyInstanceAttribute` for the `pets` attribute, which will create the `Pet` entities **after** the `User` entity is created. 31 | 32 | Also, the `pets` attribute needs to be an array, so we can use here a `CollectionSubfactory`. The behaviour is similar to `SingleSubfactory`, but it creates a collection of entities instead of only one. 33 | 34 | Some more examples could be found on both test files. 35 | -------------------------------------------------------------------------------- /examples/1-to-N-related/dataSource.ts: -------------------------------------------------------------------------------- 1 | import { DataSource } from "typeorm"; 2 | 3 | export const dataSource = new DataSource({ 4 | type: "sqlite", 5 | database: ":memory:", 6 | entities: [`${__dirname}/**/*.entity.ts`], 7 | }); 8 | -------------------------------------------------------------------------------- /examples/1-to-N-related/entities/Pet.entity.ts: -------------------------------------------------------------------------------- 1 | import { Entity, PrimaryGeneratedColumn, Column, JoinColumn, ManyToOne } from "typeorm"; 2 | import { User } from "./User.entity"; 3 | 4 | @Entity() 5 | export class Pet { 6 | @PrimaryGeneratedColumn("increment") 7 | id!: string; 8 | 9 | @Column() 10 | name!: string; 11 | 12 | @ManyToOne( 13 | () => User, 14 | (user) => user.pets, 15 | { nullable: false }, 16 | ) 17 | @JoinColumn({ name: "owner_id" }) 18 | owner!: User; 19 | } 20 | -------------------------------------------------------------------------------- /examples/1-to-N-related/entities/User.entity.ts: -------------------------------------------------------------------------------- 1 | import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn, OneToMany } from "typeorm"; 2 | import { Pet } from "./Pet.entity"; 3 | 4 | @Entity() 5 | export class User { 6 | @PrimaryGeneratedColumn("increment") 7 | id!: number; 8 | 9 | @CreateDateColumn({ name: "created_at" }) 10 | createdAt!: Date; 11 | 12 | @UpdateDateColumn({ name: "updated_at" }) 13 | updatedAt!: Date; 14 | 15 | @Column() 16 | name!: string; 17 | 18 | @Column({ name: "last_name" }) 19 | lastName!: string; 20 | 21 | @OneToMany( 22 | () => Pet, 23 | (pet) => pet.owner, 24 | { nullable: false }, 25 | ) 26 | pets!: Pet[]; 27 | } 28 | -------------------------------------------------------------------------------- /examples/1-to-N-related/factories/Pet.factory.ts: -------------------------------------------------------------------------------- 1 | import { faker } from "@faker-js/faker"; 2 | import { EagerInstanceAttribute, type FactorizedAttrs, Factory, SingleSubfactory } from "../../../src"; 3 | import { dataSource } from "../dataSource"; 4 | import { Pet } from "../entities/Pet.entity"; 5 | import { UserFactory } from "./User.factory"; 6 | 7 | export class PetFactory extends Factory { 8 | protected entity = Pet; 9 | protected dataSource = dataSource; 10 | 11 | protected attrs(): FactorizedAttrs { 12 | return { 13 | name: faker.animal.insect(), 14 | owner: new EagerInstanceAttribute((instance) => new SingleSubfactory(UserFactory, { pets: [instance] })), 15 | }; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/1-to-N-related/factories/User.factory.ts: -------------------------------------------------------------------------------- 1 | import { faker } from "@faker-js/faker"; 2 | import { type FactorizedAttrs, Factory } from "../../../src"; 3 | import { dataSource } from "../dataSource"; 4 | import { User } from "../entities/User.entity"; 5 | 6 | export class UserFactory extends Factory { 7 | protected entity = User; 8 | protected dataSource = dataSource; 9 | 10 | protected attrs(): FactorizedAttrs { 11 | return { 12 | name: faker.person.firstName(), 13 | lastName: faker.person.lastName(), 14 | pets: [], 15 | }; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/1-to-N-related/test/PetFactory.test.ts: -------------------------------------------------------------------------------- 1 | import { dataSource } from "../dataSource"; 2 | import { Pet } from "../entities/Pet.entity"; 3 | import { User } from "../entities/User.entity"; 4 | import { PetFactory } from "../factories/Pet.factory"; 5 | 6 | describe(PetFactory, () => { 7 | const factory = new PetFactory(); 8 | 9 | describe(PetFactory.prototype.make, () => { 10 | test("Should make a new entity", async () => { 11 | const petMaked = await factory.make(); 12 | 13 | expect(petMaked).toBeInstanceOf(Pet); 14 | expect(petMaked.id).toBeUndefined(); 15 | expect(petMaked.name).toBeDefined(); 16 | 17 | expect(petMaked.owner).toBeInstanceOf(User); 18 | expect(petMaked.owner.id).toBeUndefined(); 19 | expect(petMaked.owner.name).toBeDefined(); 20 | }); 21 | 22 | test("Should make two entities with different attributes", async () => { 23 | const petMaked1 = await factory.make(); 24 | const petMaked2 = await factory.make(); 25 | 26 | expect(petMaked1).not.toStrictEqual(petMaked2); 27 | }); 28 | }); 29 | 30 | describe(PetFactory.prototype.makeMany, () => { 31 | test("Should make many new entities", async () => { 32 | const count = 2; 33 | const entitiesMaked = await factory.makeMany(count); 34 | 35 | expect(entitiesMaked).toHaveLength(count); 36 | 37 | for (const entity of entitiesMaked) { 38 | expect(entity.id).toBeUndefined(); 39 | expect(entity.owner).toBeInstanceOf(User); 40 | expect(entity.owner.id).toBeUndefined(); 41 | } 42 | }); 43 | }); 44 | 45 | describe(PetFactory.prototype.create, () => { 46 | beforeAll(async () => { 47 | await dataSource.initialize(); 48 | }); 49 | 50 | beforeEach(async () => { 51 | await dataSource.synchronize(true); 52 | }); 53 | 54 | afterAll(async () => { 55 | await dataSource.destroy(); 56 | }); 57 | 58 | test("Should create a new entity", async () => { 59 | const petCreated = await factory.create(); 60 | 61 | expect(petCreated).toBeInstanceOf(Pet); 62 | expect(petCreated.id).toBeDefined(); 63 | expect(petCreated.name).toBeDefined(); 64 | 65 | expect(petCreated.owner).toBeInstanceOf(User); 66 | expect(petCreated.owner.id).toBeDefined(); 67 | expect(petCreated.owner.name).toBeDefined(); 68 | }); 69 | 70 | test("Should create two entities with different attributes", async () => { 71 | const petCreated1 = await factory.create(); 72 | const petCreated2 = await factory.create(); 73 | 74 | expect(petCreated1).not.toStrictEqual(petCreated2); 75 | }); 76 | }); 77 | 78 | describe(PetFactory.prototype.createMany, () => { 79 | beforeAll(async () => { 80 | await dataSource.initialize(); 81 | }); 82 | 83 | beforeEach(async () => { 84 | await dataSource.synchronize(true); 85 | }); 86 | 87 | afterAll(async () => { 88 | await dataSource.destroy(); 89 | }); 90 | 91 | test("Should create many new entities", async () => { 92 | const count = 2; 93 | const entitiesCreated = await factory.createMany(count); 94 | 95 | expect(entitiesCreated).toHaveLength(count); 96 | 97 | for (const entity of entitiesCreated) { 98 | expect(entity.id).toBeDefined(); 99 | expect(entity.owner).toBeInstanceOf(User); 100 | expect(entity.owner.id).toBeDefined(); 101 | } 102 | }); 103 | }); 104 | }); 105 | -------------------------------------------------------------------------------- /examples/1-to-N-related/test/UserFactory.test.ts: -------------------------------------------------------------------------------- 1 | import { CollectionSubfactory, LazyInstanceAttribute } from "../../../src"; 2 | import { dataSource } from "../dataSource"; 3 | import { Pet } from "../entities/Pet.entity"; 4 | import { User } from "../entities/User.entity"; 5 | import { PetFactory } from "../factories/Pet.factory"; 6 | import { UserFactory } from "../factories/User.factory"; 7 | 8 | describe(UserFactory, () => { 9 | const factory = new UserFactory(); 10 | 11 | describe(UserFactory.prototype.make, () => { 12 | test("Should make a new entity", async () => { 13 | const userMaked = await factory.make(); 14 | 15 | expect(userMaked).toBeInstanceOf(User); 16 | expect(userMaked.id).toBeUndefined(); 17 | expect(userMaked.name).toBeDefined(); 18 | expect(userMaked.lastName).toBeDefined(); 19 | 20 | expect(userMaked.pets).toBeInstanceOf(Array); 21 | expect(userMaked.pets).toHaveLength(0); 22 | }); 23 | 24 | test("Should make a new entity with relation", async () => { 25 | const userMaked = await factory.make({ 26 | pets: new LazyInstanceAttribute((instance) => new CollectionSubfactory(PetFactory, 1, { owner: instance })), 27 | }); 28 | 29 | expect(userMaked).toBeInstanceOf(User); 30 | expect(userMaked.id).toBeUndefined(); 31 | expect(userMaked.name).toBeDefined(); 32 | expect(userMaked.lastName).toBeDefined(); 33 | 34 | expect(userMaked.pets).toBeInstanceOf(Array); 35 | expect(userMaked.pets).toHaveLength(1); 36 | 37 | for (const pet of userMaked.pets) { 38 | expect(pet).toBeInstanceOf(Pet); 39 | expect(pet.id).toBeUndefined(); 40 | expect(pet.owner).toBeDefined(); 41 | expect(pet.owner).toEqual(userMaked); 42 | } 43 | }); 44 | 45 | test("Should make two entities with different attributes", async () => { 46 | const userMaked1 = await factory.make(); 47 | const userMaked2 = await factory.make(); 48 | 49 | expect(userMaked1).not.toStrictEqual(userMaked2); 50 | }); 51 | }); 52 | 53 | describe(UserFactory.prototype.makeMany, () => { 54 | test("Should make many new entities", async () => { 55 | const count = 2; 56 | const entitiesMaked = await factory.makeMany(count); 57 | 58 | expect(entitiesMaked).toHaveLength(count); 59 | 60 | for (const entity of entitiesMaked) { 61 | expect(entity.id).toBeUndefined(); 62 | expect(entity.pets).toBeInstanceOf(Array); 63 | expect(entity.pets).toHaveLength(0); 64 | } 65 | }); 66 | 67 | test("Should make many new entities with relations", async () => { 68 | const count = 2; 69 | const entitiesMaked = await factory.makeMany(count, { 70 | pets: new LazyInstanceAttribute((instance) => new CollectionSubfactory(PetFactory, 1, { owner: instance })), 71 | }); 72 | 73 | expect(entitiesMaked).toHaveLength(count); 74 | 75 | for (const entity of entitiesMaked) { 76 | expect(entity.id).toBeUndefined(); 77 | expect(entity.pets).toBeInstanceOf(Array); 78 | expect(entity.pets).toHaveLength(1); 79 | } 80 | }); 81 | }); 82 | 83 | describe(UserFactory.prototype.create, () => { 84 | beforeAll(async () => { 85 | await dataSource.initialize(); 86 | }); 87 | 88 | beforeEach(async () => { 89 | await dataSource.synchronize(true); 90 | }); 91 | 92 | afterAll(async () => { 93 | await dataSource.destroy(); 94 | }); 95 | 96 | test("Should create a new entity", async () => { 97 | const userCreated = await factory.create(); 98 | 99 | expect(userCreated).toBeInstanceOf(User); 100 | expect(userCreated.id).toBeDefined(); 101 | expect(userCreated.name).toBeDefined(); 102 | expect(userCreated.lastName).toBeDefined(); 103 | 104 | expect(userCreated.pets).toBeInstanceOf(Array); 105 | expect(userCreated.pets).toHaveLength(0); 106 | }); 107 | 108 | test("Should create a new entity with relation", async () => { 109 | const userCreated = await factory.create({ 110 | pets: new LazyInstanceAttribute((instance) => new CollectionSubfactory(PetFactory, 1, { owner: instance })), 111 | }); 112 | 113 | expect(userCreated).toBeInstanceOf(User); 114 | expect(userCreated.id).toBeDefined(); 115 | expect(userCreated.name).toBeDefined(); 116 | expect(userCreated.lastName).toBeDefined(); 117 | 118 | expect(userCreated.pets).toBeInstanceOf(Array); 119 | expect(userCreated.pets).toHaveLength(1); 120 | 121 | for (const pet of userCreated.pets) { 122 | expect(pet).toBeInstanceOf(Pet); 123 | expect(pet.id).toBeDefined(); 124 | expect(pet.owner).toBeDefined(); 125 | expect(pet.owner).toEqual(userCreated); 126 | } 127 | }); 128 | 129 | test("Should create one entity", async () => { 130 | await factory.create(); 131 | 132 | const [totalUsers, totalPets] = await Promise.all([ 133 | dataSource.createEntityManager().count(User), 134 | dataSource.createEntityManager().count(Pet), 135 | ]); 136 | 137 | expect(totalUsers).toBe(1); 138 | expect(totalPets).toBe(0); 139 | }); 140 | 141 | test("Should create one entity of each type", async () => { 142 | await factory.create({ 143 | pets: new LazyInstanceAttribute((instance) => new CollectionSubfactory(PetFactory, 1, { owner: instance })), 144 | }); 145 | 146 | const [totalUsers, totalPets] = await Promise.all([ 147 | dataSource.createEntityManager().count(User), 148 | dataSource.createEntityManager().count(Pet), 149 | ]); 150 | 151 | expect(totalUsers).toBe(1); 152 | expect(totalPets).toBe(1); 153 | }); 154 | 155 | test("Should create two entities with different attributes", async () => { 156 | const userCreated1 = await factory.create(); 157 | const userCreated2 = await factory.create(); 158 | 159 | expect(userCreated1).not.toStrictEqual(userCreated2); 160 | }); 161 | }); 162 | 163 | describe(UserFactory.prototype.createMany, () => { 164 | beforeAll(async () => { 165 | await dataSource.initialize(); 166 | }); 167 | 168 | beforeEach(async () => { 169 | await dataSource.synchronize(true); 170 | }); 171 | 172 | afterAll(async () => { 173 | await dataSource.destroy(); 174 | }); 175 | 176 | test("Should create many new entities", async () => { 177 | const count = 2; 178 | const entitiesMaked = await factory.createMany(count); 179 | 180 | expect(entitiesMaked).toHaveLength(count); 181 | 182 | for (const entity of entitiesMaked) { 183 | expect(entity.id).toBeDefined(); 184 | expect(entity.pets).toBeInstanceOf(Array); 185 | expect(entity.pets).toHaveLength(0); 186 | } 187 | }); 188 | 189 | test("Should create many new entities with relations", async () => { 190 | const count = 2; 191 | const entitiesMaked = await factory.createMany(count, { 192 | pets: new LazyInstanceAttribute((instance) => new CollectionSubfactory(PetFactory, 1, { owner: instance })), 193 | }); 194 | 195 | expect(entitiesMaked).toHaveLength(count); 196 | 197 | for (const entity of entitiesMaked) { 198 | expect(entity.id).toBeDefined(); 199 | expect(entity.pets).toBeInstanceOf(Array); 200 | expect(entity.pets).toHaveLength(1); 201 | } 202 | }); 203 | 204 | test("Should create many entities without relation", async () => { 205 | const count = 2; 206 | await factory.createMany(2); 207 | 208 | const [totalUsers, totalPets] = await Promise.all([ 209 | dataSource.createEntityManager().count(User), 210 | dataSource.createEntityManager().count(Pet), 211 | ]); 212 | 213 | expect(totalUsers).toBe(count); 214 | expect(totalPets).toBe(0); 215 | }); 216 | 217 | test("Should create many entities of each type with relations", async () => { 218 | const count = 2; 219 | await factory.createMany(2, { 220 | pets: new LazyInstanceAttribute((instance) => new CollectionSubfactory(PetFactory, 1, { owner: instance })), 221 | }); 222 | 223 | const [totalUsers, totalPets] = await Promise.all([ 224 | dataSource.createEntityManager().count(User), 225 | dataSource.createEntityManager().count(Pet), 226 | ]); 227 | 228 | expect(totalUsers).toBe(count); 229 | expect(totalPets).toBe(count); 230 | }); 231 | }); 232 | }); 233 | -------------------------------------------------------------------------------- /examples/N-to-M-related/README.md: -------------------------------------------------------------------------------- 1 | ## Two entities with a N-to-M relationship 2 | 3 | This example shows how to create two entities with a N-to-M relationship. The `User` entity has many `Pet` entities, which are also related to many `User` entities. 4 | 5 | ```typescript 6 | // factories/UserFactory.ts 7 | export class UserFactory extends Factory { 8 | ... 9 | protected attrs(): FactorizedAttrs { 10 | return { 11 | ... 12 | pets: [], // or 13 | pets: new LazyInstanceAttribute((instance) => new CollectionSubfactory(PetFactory, 1, { owners: [instance] })) 14 | } 15 | } 16 | } 17 | 18 | // factories/PetFactory.ts 19 | export class PetFactory extends Factory { 20 | ... 21 | protected attrs(): FactorizedAttrs { 22 | return { 23 | ... 24 | owners: [], // or 25 | owners: new EagerInstanceAttribute((instance) => new CollectionSubfactory(UserFactory, 1, { pets: [instance] })), 26 | } 27 | } 28 | } 29 | ``` 30 | 31 | In this case, the relation needs to have a different table to handle the data, so both of the entities need to use `CollectionSubfactory` to create the related entities. 32 | 33 | Some more examples could be found on both test files. 34 | -------------------------------------------------------------------------------- /examples/N-to-M-related/dataSource.ts: -------------------------------------------------------------------------------- 1 | import { DataSource } from "typeorm"; 2 | 3 | export const dataSource = new DataSource({ 4 | type: "sqlite", 5 | database: ":memory:", 6 | entities: [`${__dirname}/**/*.entity.ts`], 7 | }); 8 | -------------------------------------------------------------------------------- /examples/N-to-M-related/entities/Pet.entity.ts: -------------------------------------------------------------------------------- 1 | import { Entity, PrimaryGeneratedColumn, Column, ManyToMany } from "typeorm"; 2 | import { User } from "./User.entity"; 3 | 4 | @Entity() 5 | export class Pet { 6 | @PrimaryGeneratedColumn("increment") 7 | id!: string; 8 | 9 | @Column() 10 | name!: string; 11 | 12 | @ManyToMany( 13 | () => User, 14 | (user) => user.pets, 15 | ) 16 | owners!: User[]; 17 | } 18 | -------------------------------------------------------------------------------- /examples/N-to-M-related/entities/User.entity.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Entity, 3 | Column, 4 | PrimaryGeneratedColumn, 5 | CreateDateColumn, 6 | UpdateDateColumn, 7 | ManyToMany, 8 | JoinTable, 9 | } from "typeorm"; 10 | import { Pet } from "./Pet.entity"; 11 | 12 | @Entity() 13 | export class User { 14 | @PrimaryGeneratedColumn("increment") 15 | id!: number; 16 | 17 | @CreateDateColumn({ name: "created_at" }) 18 | createdAt!: Date; 19 | 20 | @UpdateDateColumn({ name: "updated_at" }) 21 | updatedAt!: Date; 22 | 23 | @Column() 24 | name!: string; 25 | 26 | @Column({ name: "last_name" }) 27 | lastName!: string; 28 | 29 | @ManyToMany( 30 | () => Pet, 31 | (pet) => pet.owners, 32 | ) 33 | @JoinTable() 34 | pets!: Pet[]; 35 | } 36 | -------------------------------------------------------------------------------- /examples/N-to-M-related/factories/Pet.factory.ts: -------------------------------------------------------------------------------- 1 | import { faker } from "@faker-js/faker"; 2 | import { type FactorizedAttrs, Factory } from "../../../src"; 3 | import { dataSource } from "../dataSource"; 4 | import { Pet } from "../entities/Pet.entity"; 5 | 6 | export class PetFactory extends Factory { 7 | protected entity = Pet; 8 | protected dataSource = dataSource; 9 | 10 | protected attrs(): FactorizedAttrs { 11 | return { 12 | name: faker.animal.insect(), 13 | owners: [], 14 | }; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /examples/N-to-M-related/factories/User.factory.ts: -------------------------------------------------------------------------------- 1 | import { faker } from "@faker-js/faker"; 2 | import { type FactorizedAttrs, Factory } from "../../../src"; 3 | import { dataSource } from "../dataSource"; 4 | import { User } from "../entities/User.entity"; 5 | 6 | export class UserFactory extends Factory { 7 | protected entity = User; 8 | protected dataSource = dataSource; 9 | 10 | protected attrs(): FactorizedAttrs { 11 | return { 12 | name: faker.person.firstName(), 13 | lastName: faker.person.lastName(), 14 | pets: [], 15 | }; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/N-to-M-related/test/PetFactory.test.ts: -------------------------------------------------------------------------------- 1 | import { CollectionSubfactory, EagerInstanceAttribute } from "../../../src"; 2 | import { dataSource } from "../dataSource"; 3 | import { Pet } from "../entities/Pet.entity"; 4 | import { User } from "../entities/User.entity"; 5 | import { PetFactory } from "../factories/Pet.factory"; 6 | import { UserFactory } from "../factories/User.factory"; 7 | 8 | describe(PetFactory, () => { 9 | const factory = new PetFactory(); 10 | 11 | describe(PetFactory.prototype.make, () => { 12 | test("Should make a new entity", async () => { 13 | const petMaked = await factory.make(); 14 | 15 | expect(petMaked).toBeInstanceOf(Pet); 16 | expect(petMaked.id).toBeUndefined(); 17 | expect(petMaked.name).toBeDefined(); 18 | 19 | expect(petMaked.owners).toBeInstanceOf(Array); 20 | expect(petMaked.owners).toHaveLength(0); 21 | }); 22 | 23 | test("Should make a new entity with relation", async () => { 24 | const petMaked = await factory.make({ 25 | owners: new EagerInstanceAttribute( 26 | (instance) => new CollectionSubfactory(UserFactory, 1, { pets: [instance] }), 27 | ), 28 | }); 29 | 30 | expect(petMaked).toBeInstanceOf(Pet); 31 | expect(petMaked.id).toBeUndefined(); 32 | expect(petMaked.name).toBeDefined(); 33 | 34 | expect(petMaked.owners).toBeInstanceOf(Array); 35 | expect(petMaked.owners).toHaveLength(1); 36 | 37 | for (const owner of petMaked.owners) { 38 | expect(owner).toBeInstanceOf(User); 39 | expect(owner.id).toBeUndefined(); 40 | expect(owner.pets).toBeInstanceOf(Array); 41 | expect(owner.pets).toHaveLength(1); 42 | } 43 | }); 44 | 45 | test("Should make two entities with different attributes", async () => { 46 | const petMaked1 = await factory.make(); 47 | const petMaked2 = await factory.make(); 48 | 49 | expect(petMaked1).not.toStrictEqual(petMaked2); 50 | }); 51 | }); 52 | 53 | describe(PetFactory.prototype.makeMany, () => { 54 | test("Should make many new entities", async () => { 55 | const count = 2; 56 | const entitiesMaked = await factory.makeMany(count); 57 | 58 | expect(entitiesMaked).toHaveLength(count); 59 | 60 | for (const entity of entitiesMaked) { 61 | expect(entity.id).toBeUndefined(); 62 | expect(entity.owners).toBeInstanceOf(Array); 63 | expect(entity.owners).toHaveLength(0); 64 | } 65 | }); 66 | 67 | test("Should make many new entities with relations", async () => { 68 | const count = 2; 69 | const entitiesMaked = await factory.makeMany(count, { 70 | owners: new EagerInstanceAttribute( 71 | (instance) => new CollectionSubfactory(UserFactory, 1, { pets: [instance] }), 72 | ), 73 | }); 74 | 75 | expect(entitiesMaked).toHaveLength(count); 76 | 77 | for (const entity of entitiesMaked) { 78 | expect(entity.id).toBeUndefined(); 79 | expect(entity.owners).toBeInstanceOf(Array); 80 | expect(entity.owners).toHaveLength(1); 81 | } 82 | }); 83 | }); 84 | 85 | describe(PetFactory.prototype.create, () => { 86 | beforeAll(async () => { 87 | await dataSource.initialize(); 88 | }); 89 | 90 | beforeEach(async () => { 91 | await dataSource.synchronize(true); 92 | }); 93 | 94 | afterAll(async () => { 95 | await dataSource.destroy(); 96 | }); 97 | 98 | test("Should create a new entity", async () => { 99 | const petCreated = await factory.create(); 100 | 101 | expect(petCreated).toBeInstanceOf(Pet); 102 | expect(petCreated.id).toBeDefined(); 103 | expect(petCreated.name).toBeDefined(); 104 | 105 | expect(petCreated.owners).toBeInstanceOf(Array); 106 | expect(petCreated.owners).toHaveLength(0); 107 | }); 108 | 109 | test("Should create a new entity with relation", async () => { 110 | const petCreated = await factory.create({ 111 | owners: new EagerInstanceAttribute( 112 | (instance) => new CollectionSubfactory(UserFactory, 1, { pets: [instance] }), 113 | ), 114 | }); 115 | 116 | expect(petCreated).toBeInstanceOf(Pet); 117 | expect(petCreated.id).toBeDefined(); 118 | expect(petCreated.name).toBeDefined(); 119 | 120 | expect(petCreated.owners).toBeInstanceOf(Array); 121 | expect(petCreated.owners).toHaveLength(1); 122 | 123 | for (const owner of petCreated.owners) { 124 | expect(owner).toBeInstanceOf(User); 125 | expect(owner.id).toBeDefined(); 126 | expect(owner.pets).toBeInstanceOf(Array); 127 | expect(owner.pets).toHaveLength(1); 128 | } 129 | }); 130 | 131 | test("Should create one entity", async () => { 132 | await factory.create(); 133 | 134 | const [totalUsers, totalPets] = await Promise.all([ 135 | dataSource.createEntityManager().count(User), 136 | dataSource.createEntityManager().count(Pet), 137 | ]); 138 | 139 | expect(totalUsers).toBe(0); 140 | expect(totalPets).toBe(1); 141 | }); 142 | 143 | test("Should create one entity of each type", async () => { 144 | await factory.create({ 145 | owners: new EagerInstanceAttribute( 146 | (instance) => new CollectionSubfactory(UserFactory, 1, { pets: [instance] }), 147 | ), 148 | }); 149 | 150 | const [totalUsers, totalPets] = await Promise.all([ 151 | dataSource.createEntityManager().count(User), 152 | dataSource.createEntityManager().count(Pet), 153 | ]); 154 | 155 | expect(totalUsers).toBe(1); 156 | expect(totalPets).toBe(1); 157 | }); 158 | 159 | test("Should create two entities with different attributes", async () => { 160 | const petCreated1 = await factory.create(); 161 | const petCreated2 = await factory.create(); 162 | 163 | expect(petCreated1).not.toStrictEqual(petCreated2); 164 | }); 165 | }); 166 | 167 | describe(PetFactory.prototype.createMany, () => { 168 | beforeAll(async () => { 169 | await dataSource.initialize(); 170 | }); 171 | 172 | beforeEach(async () => { 173 | await dataSource.synchronize(true); 174 | }); 175 | 176 | afterAll(async () => { 177 | await dataSource.destroy(); 178 | }); 179 | 180 | test("Should create many new entities", async () => { 181 | const count = 2; 182 | const entitiesMaked = await factory.createMany(count); 183 | 184 | expect(entitiesMaked).toHaveLength(count); 185 | 186 | for (const entity of entitiesMaked) { 187 | expect(entity.id).toBeDefined(); 188 | expect(entity.owners).toBeInstanceOf(Array); 189 | expect(entity.owners).toHaveLength(0); 190 | } 191 | }); 192 | 193 | test("Should create many new entities with relations", async () => { 194 | const count = 2; 195 | const entitiesMaked = await factory.createMany(count, { 196 | owners: new EagerInstanceAttribute( 197 | (instance) => new CollectionSubfactory(UserFactory, 1, { pets: [instance] }), 198 | ), 199 | }); 200 | 201 | expect(entitiesMaked).toHaveLength(count); 202 | 203 | for (const entity of entitiesMaked) { 204 | expect(entity.id).toBeDefined(); 205 | expect(entity.owners).toBeInstanceOf(Array); 206 | expect(entity.owners).toHaveLength(1); 207 | } 208 | }); 209 | 210 | test("Should create many entities without relation", async () => { 211 | const count = 2; 212 | await factory.createMany(2); 213 | 214 | const [totalUsers, totalPets] = await Promise.all([ 215 | dataSource.createEntityManager().count(User), 216 | dataSource.createEntityManager().count(Pet), 217 | ]); 218 | 219 | expect(totalUsers).toBe(0); 220 | expect(totalPets).toBe(count); 221 | }); 222 | 223 | test("Should create many entities of each type with relations", async () => { 224 | const count = 2; 225 | await factory.createMany(2, { 226 | owners: new EagerInstanceAttribute( 227 | (instance) => new CollectionSubfactory(UserFactory, 1, { pets: [instance] }), 228 | ), 229 | }); 230 | 231 | const [totalUsers, totalPets] = await Promise.all([ 232 | dataSource.createEntityManager().count(User), 233 | dataSource.createEntityManager().count(Pet), 234 | ]); 235 | 236 | expect(totalUsers).toBe(count); 237 | expect(totalPets).toBe(count); 238 | }); 239 | 240 | test("Should create many entities related with many other entities", async () => { 241 | const count = 2; 242 | const petsCreated = await factory.createMany(2); 243 | await new UserFactory().createMany(2, { 244 | pets: petsCreated, 245 | }); 246 | 247 | const [totalUsers, totalPets] = await Promise.all([ 248 | dataSource.createEntityManager().count(User), 249 | dataSource.createEntityManager().count(Pet), 250 | ]); 251 | 252 | expect(totalUsers).toBe(count); 253 | expect(totalPets).toBe(count); 254 | }); 255 | }); 256 | }); 257 | -------------------------------------------------------------------------------- /examples/N-to-M-related/test/UserFactory.test.ts: -------------------------------------------------------------------------------- 1 | import { CollectionSubfactory, LazyInstanceAttribute } from "../../../src"; 2 | import { dataSource } from "../dataSource"; 3 | import { Pet } from "../entities/Pet.entity"; 4 | import { User } from "../entities/User.entity"; 5 | import { PetFactory } from "../factories/Pet.factory"; 6 | import { UserFactory } from "../factories/User.factory"; 7 | 8 | describe(UserFactory, () => { 9 | const factory = new UserFactory(); 10 | 11 | describe(UserFactory.prototype.make, () => { 12 | test("Should make a new entity", async () => { 13 | const userMaked = await factory.make(); 14 | 15 | expect(userMaked).toBeInstanceOf(User); 16 | expect(userMaked.id).toBeUndefined(); 17 | expect(userMaked.name).toBeDefined(); 18 | expect(userMaked.lastName).toBeDefined(); 19 | 20 | expect(userMaked.pets).toBeInstanceOf(Array); 21 | expect(userMaked.pets).toHaveLength(0); 22 | }); 23 | 24 | test("Should make a new entity with relation", async () => { 25 | const userMaked = await factory.make({ 26 | pets: new LazyInstanceAttribute((instance) => new CollectionSubfactory(PetFactory, 1, { owners: [instance] })), 27 | }); 28 | 29 | expect(userMaked).toBeInstanceOf(User); 30 | expect(userMaked.id).toBeUndefined(); 31 | expect(userMaked.name).toBeDefined(); 32 | expect(userMaked.lastName).toBeDefined(); 33 | 34 | expect(userMaked.pets).toBeInstanceOf(Array); 35 | expect(userMaked.pets).toHaveLength(1); 36 | 37 | for (const pet of userMaked.pets) { 38 | expect(pet).toBeInstanceOf(Pet); 39 | expect(pet.id).toBeUndefined(); 40 | expect(pet.owners).toBeInstanceOf(Array); 41 | expect(pet.owners).toHaveLength(1); 42 | } 43 | }); 44 | 45 | test("Should make two entities with different attributes", async () => { 46 | const userMaked1 = await factory.make(); 47 | const userMaked2 = await factory.make(); 48 | 49 | expect(userMaked1).not.toStrictEqual(userMaked2); 50 | }); 51 | }); 52 | 53 | describe(UserFactory.prototype.makeMany, () => { 54 | test("Should make many new entities", async () => { 55 | const count = 2; 56 | const entitiesMaked = await factory.makeMany(count); 57 | 58 | expect(entitiesMaked).toHaveLength(count); 59 | 60 | for (const entity of entitiesMaked) { 61 | expect(entity.id).toBeUndefined(); 62 | expect(entity.pets).toBeInstanceOf(Array); 63 | expect(entity.pets).toHaveLength(0); 64 | } 65 | }); 66 | 67 | test("Should make many new entities with relations", async () => { 68 | const count = 2; 69 | const entitiesMaked = await factory.makeMany(count, { 70 | pets: new LazyInstanceAttribute((instance) => new CollectionSubfactory(PetFactory, 1, { owners: [instance] })), 71 | }); 72 | 73 | expect(entitiesMaked).toHaveLength(count); 74 | 75 | for (const entity of entitiesMaked) { 76 | expect(entity.id).toBeUndefined(); 77 | expect(entity.pets).toBeInstanceOf(Array); 78 | expect(entity.pets).toHaveLength(1); 79 | } 80 | }); 81 | }); 82 | 83 | describe(UserFactory.prototype.create, () => { 84 | beforeAll(async () => { 85 | await dataSource.initialize(); 86 | }); 87 | 88 | beforeEach(async () => { 89 | await dataSource.synchronize(true); 90 | }); 91 | 92 | afterAll(async () => { 93 | await dataSource.destroy(); 94 | }); 95 | 96 | test("Should create a new entity", async () => { 97 | const userCreated = await factory.create(); 98 | 99 | expect(userCreated).toBeInstanceOf(User); 100 | expect(userCreated.id).toBeDefined(); 101 | expect(userCreated.name).toBeDefined(); 102 | expect(userCreated.lastName).toBeDefined(); 103 | 104 | expect(userCreated.pets).toBeInstanceOf(Array); 105 | expect(userCreated.pets).toHaveLength(0); 106 | }); 107 | 108 | test("Should create a new entity with relation", async () => { 109 | const userCreated = await factory.create({ 110 | pets: new LazyInstanceAttribute((instance) => new CollectionSubfactory(PetFactory, 1, { owners: [instance] })), 111 | }); 112 | 113 | expect(userCreated).toBeInstanceOf(User); 114 | expect(userCreated.id).toBeDefined(); 115 | expect(userCreated.name).toBeDefined(); 116 | expect(userCreated.lastName).toBeDefined(); 117 | 118 | expect(userCreated.pets).toBeInstanceOf(Array); 119 | expect(userCreated.pets).toHaveLength(1); 120 | 121 | for (const pet of userCreated.pets) { 122 | expect(pet).toBeInstanceOf(Pet); 123 | expect(pet.id).toBeDefined(); 124 | expect(pet.owners).toBeInstanceOf(Array); 125 | expect(pet.owners).toHaveLength(1); 126 | } 127 | }); 128 | 129 | test("Should create one entity", async () => { 130 | await factory.create(); 131 | 132 | const [totalUsers, totalPets] = await Promise.all([ 133 | dataSource.createEntityManager().count(User), 134 | dataSource.createEntityManager().count(Pet), 135 | ]); 136 | 137 | expect(totalUsers).toBe(1); 138 | expect(totalPets).toBe(0); 139 | }); 140 | 141 | test("Should create one entity of each type", async () => { 142 | await factory.create({ 143 | pets: new LazyInstanceAttribute((instance) => new CollectionSubfactory(PetFactory, 1, { owners: [instance] })), 144 | }); 145 | 146 | const [totalUsers, totalPets] = await Promise.all([ 147 | dataSource.createEntityManager().count(User), 148 | dataSource.createEntityManager().count(Pet), 149 | ]); 150 | 151 | expect(totalUsers).toBe(1); 152 | expect(totalPets).toBe(1); 153 | }); 154 | 155 | test("Should create two entities with different attributes", async () => { 156 | const userCreated1 = await factory.create(); 157 | const userCreated2 = await factory.create(); 158 | 159 | expect(userCreated1).not.toStrictEqual(userCreated2); 160 | }); 161 | }); 162 | 163 | describe(UserFactory.prototype.createMany, () => { 164 | beforeAll(async () => { 165 | await dataSource.initialize(); 166 | }); 167 | 168 | beforeEach(async () => { 169 | await dataSource.synchronize(true); 170 | }); 171 | 172 | afterAll(async () => { 173 | await dataSource.destroy(); 174 | }); 175 | 176 | test("Should create many new entities", async () => { 177 | const count = 2; 178 | const entitiesMaked = await factory.createMany(count); 179 | 180 | expect(entitiesMaked).toHaveLength(count); 181 | 182 | for (const entity of entitiesMaked) { 183 | expect(entity.id).toBeDefined(); 184 | expect(entity.pets).toBeInstanceOf(Array); 185 | expect(entity.pets).toHaveLength(0); 186 | } 187 | }); 188 | 189 | test("Should create many new entities with relations", async () => { 190 | const count = 2; 191 | const entitiesMaked = await factory.createMany(count, { 192 | pets: new LazyInstanceAttribute((instance) => new CollectionSubfactory(PetFactory, 1, { owners: [instance] })), 193 | }); 194 | 195 | expect(entitiesMaked).toHaveLength(count); 196 | 197 | for (const entity of entitiesMaked) { 198 | expect(entity.id).toBeDefined(); 199 | expect(entity.pets).toBeInstanceOf(Array); 200 | expect(entity.pets).toHaveLength(1); 201 | } 202 | }); 203 | 204 | test("Should create many entities without relation", async () => { 205 | const count = 2; 206 | await factory.createMany(2); 207 | 208 | const [totalUsers, totalPets] = await Promise.all([ 209 | dataSource.createEntityManager().count(User), 210 | dataSource.createEntityManager().count(Pet), 211 | ]); 212 | 213 | expect(totalUsers).toBe(count); 214 | expect(totalPets).toBe(0); 215 | }); 216 | 217 | test("Should create many entities of each type with relations", async () => { 218 | const count = 2; 219 | await factory.createMany(2, { 220 | pets: new LazyInstanceAttribute((instance) => new CollectionSubfactory(PetFactory, 1, { owners: [instance] })), 221 | }); 222 | 223 | const [totalUsers, totalPets] = await Promise.all([ 224 | dataSource.createEntityManager().count(User), 225 | dataSource.createEntityManager().count(Pet), 226 | ]); 227 | 228 | expect(totalUsers).toBe(count); 229 | expect(totalPets).toBe(count); 230 | }); 231 | 232 | test("Should create many entities related with many other entities", async () => { 233 | const count = 2; 234 | const usersCreated = await factory.createMany(2); 235 | await new PetFactory().createMany(2, { 236 | owners: usersCreated, 237 | }); 238 | 239 | const [totalUsers, totalPets] = await Promise.all([ 240 | dataSource.createEntityManager().count(User), 241 | dataSource.createEntityManager().count(Pet), 242 | ]); 243 | 244 | expect(totalUsers).toBe(count); 245 | expect(totalPets).toBe(count); 246 | }); 247 | }); 248 | }); 249 | -------------------------------------------------------------------------------- /examples/single-entity/README.md: -------------------------------------------------------------------------------- 1 | ## Single entity 2 | 3 | This is a little example of a single entity, with just a name and a last name. The factory related just creates or makes a new instance of the entity. Nothing special. 4 | 5 | If you want to have a look on the results, please check `test/UserFactory.test.ts` file, that has specific tests for both methods. 6 | -------------------------------------------------------------------------------- /examples/single-entity/dataSource.ts: -------------------------------------------------------------------------------- 1 | import { DataSource } from "typeorm"; 2 | 3 | export const dataSource = new DataSource({ 4 | type: "sqlite", 5 | database: ":memory:", 6 | entities: [`${__dirname}/**/*.entity.ts`], 7 | }); 8 | -------------------------------------------------------------------------------- /examples/single-entity/entities/User.entity.ts: -------------------------------------------------------------------------------- 1 | import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn } from "typeorm"; 2 | 3 | @Entity() 4 | export class User { 5 | @PrimaryGeneratedColumn("increment") 6 | id!: number; 7 | 8 | @CreateDateColumn({ name: "created_at" }) 9 | createdAt!: Date; 10 | 11 | @UpdateDateColumn({ name: "updated_at" }) 12 | updatedAt!: Date; 13 | 14 | @Column() 15 | name!: string; 16 | 17 | @Column({ name: "last_name" }) 18 | lastName!: string; 19 | } 20 | -------------------------------------------------------------------------------- /examples/single-entity/factories/User.factory.ts: -------------------------------------------------------------------------------- 1 | import { faker } from "@faker-js/faker"; 2 | import { type FactorizedAttrs, Factory } from "../../../src"; 3 | import { dataSource } from "../dataSource"; 4 | import { User } from "../entities/User.entity"; 5 | 6 | export class UserFactory extends Factory { 7 | protected entity = User; 8 | protected dataSource = dataSource; 9 | 10 | protected attrs(): FactorizedAttrs { 11 | return { 12 | name: faker.person.firstName(), 13 | lastName: faker.person.lastName(), 14 | }; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /examples/single-entity/test/UserFactory.test.ts: -------------------------------------------------------------------------------- 1 | import { dataSource } from "../dataSource"; 2 | import { User } from "../entities/User.entity"; 3 | import { UserFactory } from "../factories/User.factory"; 4 | 5 | describe(UserFactory, () => { 6 | const factory = new UserFactory(); 7 | 8 | describe(UserFactory.prototype.make, () => { 9 | test("Should make a new entity", async () => { 10 | const userMaked = await factory.make(); 11 | 12 | expect(userMaked).toBeInstanceOf(User); 13 | expect(userMaked.id).toBeUndefined(); 14 | expect(userMaked.name).toBeDefined(); 15 | expect(userMaked.lastName).toBeDefined(); 16 | }); 17 | 18 | test("Should make two entities with different attributes", async () => { 19 | const userMaked1 = await factory.make(); 20 | const userMaked2 = await factory.make(); 21 | 22 | expect(userMaked1).not.toStrictEqual(userMaked2); 23 | }); 24 | }); 25 | 26 | describe(UserFactory.prototype.makeMany, () => { 27 | test("Should make many new entities", async () => { 28 | const count = 2; 29 | const entitiesMaked = await factory.makeMany(count); 30 | 31 | expect(entitiesMaked).toHaveLength(count); 32 | 33 | for (const entity of entitiesMaked) { 34 | expect(entity.id).toBeUndefined(); 35 | } 36 | }); 37 | }); 38 | 39 | describe(UserFactory.prototype.create, () => { 40 | beforeAll(async () => { 41 | await dataSource.initialize(); 42 | }); 43 | 44 | beforeEach(async () => { 45 | await dataSource.synchronize(true); 46 | }); 47 | 48 | afterAll(async () => { 49 | await dataSource.destroy(); 50 | }); 51 | 52 | test("Should create a new entity", async () => { 53 | const userCreated = await factory.create(); 54 | 55 | expect(userCreated).toBeInstanceOf(User); 56 | expect(userCreated.id).toBeDefined(); 57 | expect(userCreated.name).toBeDefined(); 58 | expect(userCreated.lastName).toBeDefined(); 59 | }); 60 | 61 | test("Should create two entities with different attributes", async () => { 62 | const userCreated1 = await factory.create(); 63 | const userCreated2 = await factory.create(); 64 | 65 | expect(userCreated1).not.toStrictEqual(userCreated2); 66 | }); 67 | }); 68 | 69 | describe(UserFactory.prototype.createMany, () => { 70 | beforeAll(async () => { 71 | await dataSource.initialize(); 72 | }); 73 | 74 | beforeEach(async () => { 75 | await dataSource.synchronize(true); 76 | }); 77 | 78 | afterAll(async () => { 79 | await dataSource.destroy(); 80 | }); 81 | 82 | test("Should create many new entities", async () => { 83 | const count = 2; 84 | const entitiesCreated = await factory.createMany(count); 85 | 86 | expect(entitiesCreated).toHaveLength(count); 87 | 88 | for (const entity of entitiesCreated) { 89 | expect(entity.id).toBeDefined(); 90 | } 91 | }); 92 | }); 93 | }); 94 | -------------------------------------------------------------------------------- /jest.config.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | collectCoverageFrom: ["src/**/!(*.d).ts"], 3 | preset: "ts-jest", 4 | testEnvironment: "node", 5 | }; 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Jorge Bodega (https://github.com/jorgebodega)", 3 | "dependencies": { 4 | "tslib": "2.7.0" 5 | }, 6 | "description": "🌱 A delightful way to use factories in your code.", 7 | "devDependencies": { 8 | "@biomejs/biome": "^1.8.2", 9 | "@faker-js/faker": "9.0.3", 10 | "@tsconfig/node18-strictest": "1.0.0", 11 | "@types/jest": "29.5.12", 12 | "@types/node": "20.16.8", 13 | "jest": "29.7.0", 14 | "rimraf": "6.0.1", 15 | "sqlite3": "5.1.7", 16 | "ts-jest": "29.2.5", 17 | "ts-node": "10.9.2", 18 | "typeorm": "0.3.20", 19 | "typescript": "5.6.2" 20 | }, 21 | "engines": { 22 | "node": ">=18 <19 || >=20" 23 | }, 24 | "keywords": [ 25 | "typeorm", 26 | "factory", 27 | "entity", 28 | "orm" 29 | ], 30 | "license": "MIT", 31 | "main": "dist/index.js", 32 | "name": "@jorgebodega/typeorm-factory", 33 | "packageManager": "pnpm@9.11.0", 34 | "peerDependencies": { 35 | "typeorm": "^0.3.0" 36 | }, 37 | "repository": { 38 | "type": "git", 39 | "url": "https://github.com/jorgebodega/typeorm-factory.git" 40 | }, 41 | "scripts": { 42 | "build": "tsc --project ./tsconfig.build.json", 43 | "checks": "pnpm format:ci && pnpm lint:ci && pnpm typecheck", 44 | "format": "biome format --write", 45 | "format:ci": "biome format", 46 | "lint": "biome lint", 47 | "lint:fix": "biome lint --fix", 48 | "lint:ci": "biome lint", 49 | "prebuild": "rimraf dist", 50 | "test:ci": "jest --silent", 51 | "test:cov": "jest --coverage --silent", 52 | "test:watch": "jest --watch", 53 | "test": "jest", 54 | "typecheck": "tsc --noEmit" 55 | }, 56 | "types": "dist/index.d.ts", 57 | "version": "2.1.0" 58 | } 59 | -------------------------------------------------------------------------------- /src/factory.ts: -------------------------------------------------------------------------------- 1 | import type { DataSource, SaveOptions } from "typeorm"; 2 | import { EagerInstanceAttribute, LazyInstanceAttribute } from "./instanceAttributes"; 3 | import { BaseSubfactory } from "./subfactories"; 4 | import type { Constructable, FactorizedAttrs } from "./types"; 5 | 6 | export abstract class Factory { 7 | protected abstract entity: Constructable; 8 | protected abstract dataSource: DataSource; 9 | protected abstract attrs(): FactorizedAttrs; 10 | 11 | /** 12 | * Make a new entity without persisting it 13 | */ 14 | async make(overrideParams: Partial> = {}): Promise { 15 | const attrs: FactorizedAttrs = { ...this.attrs(), ...overrideParams }; 16 | 17 | const entity = await this.makeEntity(attrs, false); 18 | 19 | await this.applyEagerInstanceAttributes(entity, attrs, false); 20 | await this.applyLazyInstanceAttributes(entity, attrs, false); 21 | 22 | return entity; 23 | } 24 | 25 | /** 26 | * Make many new entities without persisting it 27 | */ 28 | async makeMany(amount: number, overrideParams: Partial> = {}): Promise { 29 | const list = []; 30 | for (let index = 0; index < amount; index++) { 31 | list[index] = await this.make(overrideParams); 32 | } 33 | return list; 34 | } 35 | 36 | /** 37 | * Create a new entity and persist it 38 | */ 39 | async create(overrideParams: Partial> = {}, saveOptions?: SaveOptions): Promise { 40 | const attrs: FactorizedAttrs = { ...this.attrs(), ...overrideParams }; 41 | const preloadedAttrs = Object.entries(attrs).filter(([, value]) => !(value instanceof LazyInstanceAttribute)); 42 | 43 | const entity = await this.makeEntity(Object.fromEntries(preloadedAttrs) as FactorizedAttrs, true); 44 | await this.applyEagerInstanceAttributes(entity, attrs, true); 45 | 46 | const em = this.getEntityManager(); 47 | const savedEntity = await em.save(entity, saveOptions); 48 | 49 | await this.applyLazyInstanceAttributes(savedEntity, attrs, true); 50 | return em.save(savedEntity, saveOptions); 51 | } 52 | 53 | /** 54 | * Create many new entities and persist them 55 | */ 56 | async createMany( 57 | amount: number, 58 | overrideParams: Partial> = {}, 59 | saveOptions?: SaveOptions, 60 | ): Promise { 61 | const list = []; 62 | for (let index = 0; index < amount; index++) { 63 | list[index] = await this.create(overrideParams, saveOptions); 64 | } 65 | return list; 66 | } 67 | 68 | protected getEntityManager() { 69 | return this.dataSource.createEntityManager(); 70 | } 71 | 72 | private async makeEntity(attrs: FactorizedAttrs, shouldPersist: boolean) { 73 | const entity = new this.entity(); 74 | 75 | await Promise.all( 76 | Object.entries(attrs).map(async ([key, value]) => { 77 | Object.assign(entity, { [key]: await Factory.resolveValue(value, shouldPersist) }); 78 | }), 79 | ); 80 | 81 | return entity; 82 | } 83 | 84 | private async applyEagerInstanceAttributes(entity: T, attrs: FactorizedAttrs, shouldPersist: boolean) { 85 | await Promise.all( 86 | Object.entries(attrs).map(async ([key, value]) => { 87 | if (value instanceof EagerInstanceAttribute) { 88 | const newAttrib = value.resolve(entity); 89 | Object.assign(entity, { [key]: await Factory.resolveValue(newAttrib, shouldPersist) }); 90 | } 91 | }), 92 | ); 93 | } 94 | 95 | private async applyLazyInstanceAttributes(entity: T, attrs: FactorizedAttrs, shouldPersist: boolean) { 96 | await Promise.all( 97 | Object.entries(attrs).map(async ([key, value]) => { 98 | if (value instanceof LazyInstanceAttribute) { 99 | const newAttrib = value.resolve(entity); 100 | Object.assign(entity, { [key]: await Factory.resolveValue(newAttrib, shouldPersist) }); 101 | } 102 | }), 103 | ); 104 | } 105 | 106 | private static async resolveValue(value: unknown, shouldPersist: boolean): Promise { 107 | if (value instanceof BaseSubfactory) { 108 | return shouldPersist ? value.create() : value.make(); 109 | } 110 | if (Array.isArray(value)) { 111 | return await Promise.all(value.map((val: unknown) => Factory.resolveValue(val, shouldPersist))); 112 | } 113 | if (value instanceof Function) { 114 | return value(); 115 | } 116 | return value; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./factory"; 2 | export * from "./instanceAttributes"; 3 | export * from "./subfactories"; 4 | export * from "./types"; 5 | -------------------------------------------------------------------------------- /src/instanceAttributes/eagerInstanceAttribute.ts: -------------------------------------------------------------------------------- 1 | import { InstanceAttribute } from "./instanceAttribute"; 2 | 3 | export class EagerInstanceAttribute extends InstanceAttribute {} 4 | -------------------------------------------------------------------------------- /src/instanceAttributes/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./eagerInstanceAttribute"; 2 | export * from "./instanceAttribute"; 3 | export * from "./lazyInstanceAttribute"; 4 | -------------------------------------------------------------------------------- /src/instanceAttributes/instanceAttribute.ts: -------------------------------------------------------------------------------- 1 | import type { InstanceAttributeCallback } from "../types"; 2 | 3 | export abstract class InstanceAttribute { 4 | constructor(private callback: InstanceAttributeCallback) {} 5 | 6 | resolve(entity: T) { 7 | return this.callback(entity); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/instanceAttributes/lazyInstanceAttribute.ts: -------------------------------------------------------------------------------- 1 | import { InstanceAttribute } from "./instanceAttribute"; 2 | 3 | export class LazyInstanceAttribute extends InstanceAttribute {} 4 | -------------------------------------------------------------------------------- /src/subfactories/baseSubfactory.ts: -------------------------------------------------------------------------------- 1 | import { Factory } from "../factory"; 2 | import type { Constructable, FactorizedAttrs } from "../types"; 3 | 4 | export abstract class BaseSubfactory { 5 | protected factoryInstance: Factory; 6 | 7 | constructor( 8 | factoryOrFactoryInstance: Constructable> | Factory, 9 | protected values?: Partial>, 10 | ) { 11 | this.factoryInstance = 12 | factoryOrFactoryInstance instanceof Factory ? factoryOrFactoryInstance : new factoryOrFactoryInstance(); 13 | } 14 | 15 | abstract create(): Promise | Promise; 16 | abstract make(): Promise | Promise; 17 | } 18 | -------------------------------------------------------------------------------- /src/subfactories/collectionSubfactory.ts: -------------------------------------------------------------------------------- 1 | import type { Factory } from "../factory"; 2 | import type { Constructable, FactorizedAttrs } from "../types"; 3 | import { BaseSubfactory } from "./baseSubfactory"; 4 | 5 | export class CollectionSubfactory extends BaseSubfactory { 6 | constructor( 7 | factoryOrFactoryInstance: Constructable> | Factory, 8 | private count: number, 9 | values?: Partial>, 10 | ) { 11 | super(factoryOrFactoryInstance, values); 12 | } 13 | 14 | create() { 15 | return this.factoryInstance.createMany(this.count, this.values); 16 | } 17 | 18 | make() { 19 | return this.factoryInstance.makeMany(this.count, this.values); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/subfactories/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./baseSubfactory"; 2 | export * from "./collectionSubfactory"; 3 | export * from "./singleSubfactory"; 4 | -------------------------------------------------------------------------------- /src/subfactories/singleSubfactory.ts: -------------------------------------------------------------------------------- 1 | import { BaseSubfactory } from "./baseSubfactory"; 2 | 3 | export class SingleSubfactory extends BaseSubfactory { 4 | create() { 5 | return this.factoryInstance.create(this.values); 6 | } 7 | 8 | make() { 9 | return this.factoryInstance.make(this.values); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import type { InstanceAttribute } from "./instanceAttributes"; 2 | import type { CollectionSubfactory, SingleSubfactory } from "./subfactories"; 3 | 4 | export type FactorizedAttrs = { 5 | [K in keyof Partial]: FactorizedAttr | InstanceAttribute>; 6 | }; 7 | 8 | export type FactorizedAttr = V extends Array ? ArrayFactorizedAttr : SingleFactorizedAttr; 9 | 10 | // If the factorized attr is a single value, then it should be the same type or resolve to that value 11 | export type SingleFactorizedAttr = V | (() => V | Promise) | SingleSubfactory>; 12 | // If the factorized attr is a list, then it should be a list of SingleFactorizedAttr or a collection subfactory 13 | export type ArrayFactorizedAttr = Array> | CollectionSubfactory>; 14 | 15 | // The function of an instance attribute must resolve always to a FactorizedAttr 16 | export type InstanceAttributeCallback = (entity: T) => FactorizedAttr; 17 | 18 | // Helper types 19 | export type Constructable = new () => T; 20 | export type IsObject = T extends object ? T : never; 21 | -------------------------------------------------------------------------------- /test/factory.test.ts: -------------------------------------------------------------------------------- 1 | import { CollectionSubfactory, EagerInstanceAttribute, Factory, LazyInstanceAttribute, SingleSubfactory } from "../src"; 2 | import { Pet } from "./fixtures/Pet.entity"; 3 | import { PetFactory } from "./fixtures/Pet.factory"; 4 | import { User } from "./fixtures/User.entity"; 5 | import { UserFactory } from "./fixtures/User.factory"; 6 | import { dataSource } from "./fixtures/dataSource"; 7 | 8 | describe(Factory, () => { 9 | describe(Factory.prototype.make, () => { 10 | describe(UserFactory, () => { 11 | const factory = new UserFactory(); 12 | 13 | test("Should make a new entity", async () => { 14 | const userMaked = await factory.make(); 15 | 16 | expect(userMaked).toBeInstanceOf(User); 17 | expect(userMaked.id).toBeUndefined(); 18 | expect(userMaked.name).toBeDefined(); 19 | expect(userMaked.lastName).toBeDefined(); 20 | expect(userMaked.age).toBeDefined(); 21 | expect(userMaked.email).toBeDefined(); 22 | 23 | expect(userMaked.pets).toBeInstanceOf(Array); 24 | expect(userMaked.pets).toHaveLength(0); 25 | }); 26 | 27 | test("Should make a new entity with attribute overrided", async () => { 28 | const userMaked = await factory.make({ 29 | name: "john", 30 | }); 31 | 32 | expect(userMaked.name).toBe("john"); 33 | }); 34 | 35 | test("Should make a new entity with function as attribute", async () => { 36 | const userMaked = await factory.make({ 37 | name: () => "john", 38 | }); 39 | 40 | expect(userMaked.name).toBe("john"); 41 | }); 42 | 43 | test("Should make a new entity with async function as attribute", async () => { 44 | const userMaked = await factory.make({ 45 | name: async () => "john", 46 | }); 47 | 48 | expect(userMaked.name).toBe("john"); 49 | }); 50 | 51 | test("Should make a new entity with instance attributes", async () => { 52 | const userMaked = await factory.make({ 53 | email: new EagerInstanceAttribute((instance) => 54 | [instance.name.toLowerCase(), instance.lastName.toLowerCase(), "@email.com"].join(""), 55 | ), 56 | }); 57 | 58 | expect(userMaked.email).toMatch(userMaked.name.toLowerCase()); 59 | expect(userMaked.email).toMatch(userMaked.lastName.toLowerCase()); 60 | }); 61 | 62 | test("Should make a new entity with lazy instance attributes", async () => { 63 | const userMaked = await factory.make({ 64 | email: new EagerInstanceAttribute((instance) => 65 | [instance.name.toLowerCase(), instance.lastName.toLowerCase(), "@email.com"].join(""), 66 | ), 67 | }); 68 | 69 | expect(userMaked.email).toMatch(userMaked.name.toLowerCase()); 70 | expect(userMaked.email).toMatch(userMaked.lastName.toLowerCase()); 71 | }); 72 | 73 | test("Should make a new entity with multiple subfactories", async () => { 74 | const userMaked = await factory.make({ 75 | pets: new LazyInstanceAttribute((instance) => new CollectionSubfactory(PetFactory, 1, { owner: instance })), 76 | }); 77 | 78 | expect(userMaked.pets).toBeInstanceOf(Array); 79 | expect(userMaked.pets).toHaveLength(1); 80 | 81 | for (const pet of userMaked.pets) { 82 | expect(pet.id).toBeUndefined(); 83 | expect(pet.owner).toBeInstanceOf(User); 84 | expect(pet.owner.id).toBeUndefined(); 85 | } 86 | }); 87 | 88 | test("Should make a new entity with multiple subfactories in an array", async () => { 89 | const userMaked = await factory.make({ 90 | pets: new LazyInstanceAttribute((instance) => [new SingleSubfactory(PetFactory, { owner: instance })]), 91 | }); 92 | 93 | expect(userMaked.pets).toBeInstanceOf(Array); 94 | expect(userMaked.pets).toHaveLength(1); 95 | for (const user of userMaked.pets) { 96 | expect(user.id).toBeUndefined(); 97 | expect(user.owner).toBeInstanceOf(User); 98 | expect(user.owner.id).toBeUndefined(); 99 | } 100 | }); 101 | 102 | test("Should make a new entity with multiple existing subfactories", async () => { 103 | const petFactory = new PetFactory(); 104 | 105 | const userMaked = await factory.make({ 106 | pets: new LazyInstanceAttribute((instance) => new CollectionSubfactory(petFactory, 1, { owner: instance })), 107 | }); 108 | 109 | expect(userMaked.pets).toBeInstanceOf(Array); 110 | expect(userMaked.pets).toHaveLength(1); 111 | 112 | for (const pet of userMaked.pets) { 113 | expect(pet.id).toBeUndefined(); 114 | expect(pet.owner).toBeInstanceOf(User); 115 | expect(pet.owner.id).toBeUndefined(); 116 | } 117 | }); 118 | 119 | test("Should make two entities with different attributes", async () => { 120 | const userMaked1 = await factory.make(); 121 | const userMaked2 = await factory.make(); 122 | 123 | expect(userMaked1).not.toStrictEqual(userMaked2); 124 | }); 125 | }); 126 | 127 | describe(PetFactory, () => { 128 | const factory = new PetFactory(); 129 | 130 | test("Should make a new entity with single subfactory", async () => { 131 | const petMaked = await factory.make(); 132 | 133 | expect(petMaked).toBeInstanceOf(Pet); 134 | expect(petMaked.id).toBeUndefined(); 135 | expect(petMaked.name).toBeDefined(); 136 | expect(petMaked.owner).toBeDefined(); 137 | expect(petMaked.owner).toBeInstanceOf(User); 138 | expect(petMaked.owner.id).toBeUndefined(); 139 | }); 140 | 141 | test("Should make a new entity with single existing subfactory", async () => { 142 | const userFactory = new UserFactory(); 143 | 144 | const petMaked = await factory.make({ 145 | owner: new LazyInstanceAttribute((instance) => new SingleSubfactory(userFactory, { pets: [instance] })), 146 | }); 147 | 148 | expect(petMaked).toBeInstanceOf(Pet); 149 | expect(petMaked.id).toBeUndefined(); 150 | expect(petMaked.name).toBeDefined(); 151 | expect(petMaked.owner).toBeDefined(); 152 | expect(petMaked.owner).toBeInstanceOf(User); 153 | expect(petMaked.owner.id).toBeUndefined(); 154 | }); 155 | }); 156 | }); 157 | 158 | describe(Factory.prototype.makeMany, () => { 159 | test("Should make many new entities", async () => { 160 | const count = 2; 161 | const factory = new UserFactory(); 162 | const entitiesMaked = await factory.makeMany(count); 163 | 164 | expect(entitiesMaked).toHaveLength(count); 165 | 166 | for (const entity of entitiesMaked) { 167 | expect(entity.id).toBeUndefined(); 168 | } 169 | }); 170 | }); 171 | 172 | describe(Factory.prototype.create, () => { 173 | beforeAll(async () => { 174 | await dataSource.initialize(); 175 | }); 176 | 177 | beforeEach(async () => { 178 | await dataSource.synchronize(true); 179 | }); 180 | 181 | afterAll(async () => { 182 | await dataSource.destroy(); 183 | }); 184 | 185 | describe(UserFactory, () => { 186 | const factory = new UserFactory(); 187 | 188 | test("Should create a new entity", async () => { 189 | const userCreated = await factory.create(); 190 | 191 | expect(userCreated).toBeInstanceOf(User); 192 | expect(userCreated.id).toBeDefined(); 193 | expect(userCreated.name).toBeDefined(); 194 | expect(userCreated.lastName).toBeDefined(); 195 | expect(userCreated.age).toBeDefined(); 196 | expect(userCreated.email).toBeDefined(); 197 | 198 | expect(userCreated.pets).toBeInstanceOf(Array); 199 | expect(userCreated.pets).toHaveLength(0); 200 | }); 201 | 202 | test("Should create a new entity with attribute overrided", async () => { 203 | const userCreated = await factory.create({ 204 | name: "john", 205 | }); 206 | 207 | expect(userCreated.name).toBe("john"); 208 | }); 209 | 210 | test("Should create a new entity with function as attribute", async () => { 211 | const userCreated = await factory.create({ 212 | name: () => "john", 213 | }); 214 | 215 | expect(userCreated.name).toBe("john"); 216 | }); 217 | 218 | test("Should create a new entity with async function as attribute", async () => { 219 | const userCreated = await factory.create({ 220 | name: async () => "john", 221 | }); 222 | 223 | expect(userCreated.name).toBe("john"); 224 | }); 225 | 226 | test("Should create a new entity with instance attributes", async () => { 227 | const userCreated = await factory.create({ 228 | email: new EagerInstanceAttribute((instance) => 229 | [instance.name.toLowerCase(), instance.lastName.toLowerCase(), "@email.com"].join(""), 230 | ), 231 | }); 232 | 233 | expect(userCreated.email).toMatch(userCreated.name.toLowerCase()); 234 | expect(userCreated.email).toMatch(userCreated.lastName.toLowerCase()); 235 | }); 236 | 237 | test("Should create a new entity with lazy instance attributes", async () => { 238 | const userCreated = await factory.create({ 239 | email: new EagerInstanceAttribute((instance) => 240 | [instance.name.toLowerCase(), instance.lastName.toLowerCase(), "@email.com"].join(""), 241 | ), 242 | }); 243 | 244 | expect(userCreated.email).toMatch(userCreated.name.toLowerCase()); 245 | expect(userCreated.email).toMatch(userCreated.lastName.toLowerCase()); 246 | }); 247 | 248 | test("Should create a new entity with multiple subfactories", async () => { 249 | const userCreated = await factory.create({ 250 | pets: new LazyInstanceAttribute((instance) => new CollectionSubfactory(PetFactory, 1, { owner: instance })), 251 | }); 252 | 253 | expect(userCreated.pets).toBeInstanceOf(Array); 254 | expect(userCreated.pets).toHaveLength(1); 255 | 256 | for (const pet of userCreated.pets) { 257 | expect(pet.id).toBeDefined(); 258 | expect(pet.owner).toBeInstanceOf(User); 259 | expect(pet.owner.id).toBeDefined(); 260 | expect(pet.owner.id).toBe(userCreated.id); 261 | } 262 | }); 263 | 264 | test("Should make a new entity with multiple subfactories in an array", async () => { 265 | const userCreated = await factory.create({ 266 | pets: new LazyInstanceAttribute((instance) => [new SingleSubfactory(PetFactory, { owner: instance })]), 267 | }); 268 | 269 | expect(userCreated.pets).toBeInstanceOf(Array); 270 | expect(userCreated.pets).toHaveLength(1); 271 | 272 | for (const pet of userCreated.pets) { 273 | expect(pet.id).toBeDefined(); 274 | expect(pet.owner).toBeInstanceOf(User); 275 | expect(pet.owner.id).toBeDefined(); 276 | expect(pet.owner.id).toBe(userCreated.id); 277 | } 278 | }); 279 | 280 | test("Should create two entities with different attributes", async () => { 281 | const userCreated1 = await factory.create(); 282 | const userCreated2 = await factory.create(); 283 | 284 | expect(userCreated1).not.toStrictEqual(userCreated2); 285 | }); 286 | }); 287 | 288 | describe(PetFactory, () => { 289 | const factory = new PetFactory(); 290 | 291 | test("Should create a new entity with single subfactory", async () => { 292 | const petCreated = await factory.create(); 293 | 294 | expect(petCreated).toBeInstanceOf(Pet); 295 | expect(petCreated.id).toBeDefined(); 296 | expect(petCreated.name).toBeDefined(); 297 | expect(petCreated.owner).toBeDefined(); 298 | expect(petCreated.owner).toBeInstanceOf(User); 299 | expect(petCreated.owner.id).toBeDefined(); 300 | }); 301 | }); 302 | }); 303 | 304 | describe(Factory.prototype.createMany, () => { 305 | beforeAll(async () => { 306 | await dataSource.initialize(); 307 | }); 308 | 309 | beforeEach(async () => { 310 | await dataSource.synchronize(true); 311 | }); 312 | 313 | afterAll(async () => { 314 | await dataSource.destroy(); 315 | }); 316 | 317 | test("Should create many new entities", async () => { 318 | const count = 2; 319 | const factory = new UserFactory(); 320 | const entitiesCreated = await factory.createMany(count); 321 | 322 | expect(entitiesCreated).toHaveLength(count); 323 | for (const entity of entitiesCreated) { 324 | expect(entity.id).toBeDefined(); 325 | } 326 | }); 327 | }); 328 | }); 329 | -------------------------------------------------------------------------------- /test/fixtures/Pet.entity.ts: -------------------------------------------------------------------------------- 1 | import { Entity, PrimaryGeneratedColumn, Column, JoinColumn, ManyToOne } from "typeorm"; 2 | import { User } from "./User.entity"; 3 | 4 | @Entity() 5 | export class Pet { 6 | @PrimaryGeneratedColumn("increment") 7 | id!: string; 8 | 9 | @Column() 10 | name!: string; 11 | 12 | @ManyToOne( 13 | () => User, 14 | (user) => user.pets, 15 | ) 16 | @JoinColumn({ name: "owner_id" }) 17 | owner!: User; 18 | } 19 | -------------------------------------------------------------------------------- /test/fixtures/Pet.factory.ts: -------------------------------------------------------------------------------- 1 | import { faker } from "@faker-js/faker"; 2 | import { LazyInstanceAttribute, SingleSubfactory } from "../../src"; 3 | import { Factory } from "../../src/factory"; 4 | import type { FactorizedAttrs } from "../../src/types"; 5 | import { Pet } from "./Pet.entity"; 6 | import { UserFactory } from "./User.factory"; 7 | import { dataSource } from "./dataSource"; 8 | 9 | export class PetFactory extends Factory { 10 | protected entity = Pet; 11 | protected dataSource = dataSource; 12 | protected attrs(): FactorizedAttrs { 13 | return { 14 | name: faker.animal.insect(), 15 | owner: new LazyInstanceAttribute((instance) => new SingleSubfactory(UserFactory, { pets: [instance] })), 16 | }; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /test/fixtures/User.entity.ts: -------------------------------------------------------------------------------- 1 | import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn, OneToMany } from "typeorm"; 2 | import { Pet } from "./Pet.entity"; 3 | 4 | @Entity() 5 | export class User { 6 | @PrimaryGeneratedColumn("increment") 7 | id!: number; 8 | 9 | @CreateDateColumn({ name: "created_at" }) 10 | createdAt!: Date; 11 | 12 | @UpdateDateColumn({ name: "updated_at" }) 13 | updatedAt!: Date; 14 | 15 | @Column() 16 | name!: string; 17 | 18 | @Column({ name: "last_name" }) 19 | lastName!: string; 20 | 21 | @Column({ name: "second_last_name", nullable: true }) 22 | secondLastName?: string; 23 | 24 | @Column() 25 | age!: number; 26 | 27 | @Column() 28 | email!: string; 29 | 30 | @OneToMany( 31 | () => Pet, 32 | (pet) => pet.owner, 33 | ) 34 | pets!: Pet[]; 35 | } 36 | -------------------------------------------------------------------------------- /test/fixtures/User.factory.ts: -------------------------------------------------------------------------------- 1 | import { faker } from "@faker-js/faker"; 2 | import { Factory } from "../../src/factory"; 3 | import type { FactorizedAttrs } from "../../src/types"; 4 | import { User } from "../fixtures/User.entity"; 5 | import { dataSource } from "./dataSource"; 6 | 7 | export class UserFactory extends Factory { 8 | protected entity = User; 9 | protected dataSource = dataSource; 10 | 11 | protected attrs(): FactorizedAttrs { 12 | return { 13 | name: faker.person.firstName(), 14 | lastName: faker.person.lastName(), 15 | age: faker.number.int({ min: 18, max: 65 }), 16 | email: faker.internet.email(), 17 | pets: [], 18 | }; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /test/fixtures/dataSource.ts: -------------------------------------------------------------------------------- 1 | import { DataSource } from "typeorm"; 2 | 3 | export const dataSource = new DataSource({ 4 | type: "sqlite", 5 | database: ":memory:", 6 | entities: ["test/fixtures/**/*.entity.ts"], 7 | }); 8 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration": true, 4 | "importHelpers": true, 5 | "outDir": "dist", 6 | "sourceMap": true, 7 | "target": "ES2021" 8 | }, 9 | "exclude": ["dist", "coverage", "examples", "jest.config.ts", "node_modules", "test"], 10 | "extends": "./tsconfig.json" 11 | } 12 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowUnreachableCode": false, 4 | "allowUnusedLabels": false, 5 | "checkJs": true, 6 | "emitDecoratorMetadata": true, 7 | "esModuleInterop": true, 8 | "exactOptionalPropertyTypes": true, 9 | "experimentalDecorators": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "isolatedModules": true, 12 | "lib": ["es2023"], 13 | "module": "node16", 14 | "moduleResolution": "node16", 15 | "noFallthroughCasesInSwitch": true, 16 | "noImplicitOverride": true, 17 | "noImplicitReturns": true, 18 | "noPropertyAccessFromIndexSignature": true, 19 | "noUncheckedIndexedAccess": true, 20 | "noUnusedLocals": true, 21 | "noUnusedParameters": true, 22 | "skipLibCheck": true, 23 | "strict": true, 24 | "target": "es2022" 25 | }, 26 | "exclude": ["dist", "coverage"] 27 | } 28 | --------------------------------------------------------------------------------