├── .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 |
5 |
6 |
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 |
--------------------------------------------------------------------------------