diff --git a/README.md b/README.md
index 1aad8fe..152d559 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
@nestbolt/factory
- Laravel-like model factories and database seeders for NestJS with TypeORM.
+ Model factories and database seeders for NestJS with TypeORM.
@@ -407,10 +407,6 @@ Please see [CONTRIBUTING](CONTRIBUTING.md) for details.
If you discover any security-related issues, please report them via [GitHub Issues](https://github.com/nestbolt/factory/issues) with the **security** label instead of using the public issue tracker.
-## Credits
-
-- Inspired by [Laravel's](https://laravel.com) model factories and [database seeders](https://laravel.com/docs/seeding)
-
## License
The MIT License (MIT). Please see [License File](LICENSE.md) for more information.
diff --git a/package.json b/package.json
index a059491..c03cc36 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,7 @@
"name": "@nestbolt/factory",
"version": "0.1.0",
"packageManager": "pnpm@9.15.4",
- "description": "Laravel-like model factories and database seeders for NestJS with TypeORM.",
+ "description": "Model factories and database seeders for NestJS with TypeORM.",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"exports": {
diff --git a/test/factory-builder.spec.ts b/test/factory-builder.spec.ts
index 1b3f28c..869e60a 100644
--- a/test/factory-builder.spec.ts
+++ b/test/factory-builder.spec.ts
@@ -7,7 +7,7 @@ import { FactoryService } from "../src/factory.service";
import { FactoryBuilder } from "../src/factory-builder";
import { Sequence } from "../src/sequence";
import { FACTORY_OPTIONS } from "../src/factory.constants";
-import { User, Post, UserFactory, PostFactory, CommentFactory } from "./helpers/test-entities";
+import { User, Post, UserFactory, PostFactory, CommentFactory, UserFactoryWithHooks } from "./helpers/test-entities";
describe("FactoryBuilder", () => {
let module: TestingModule;
@@ -27,7 +27,7 @@ describe("FactoryBuilder", () => {
{
provide: FACTORY_OPTIONS,
useValue: {
- factories: [UserFactory, PostFactory, CommentFactory],
+ factories: [UserFactory, PostFactory, CommentFactory, UserFactoryWithHooks],
},
},
FactoryService,
@@ -107,6 +107,12 @@ describe("FactoryBuilder", () => {
expect(user.role).toBe("admin");
});
+ it("should ignore non-existent named state", async () => {
+ const user = (await service.use(UserFactory).state("nonExistentState").make()) as User;
+ expect(user).toBeDefined();
+ expect(user.role).toBe("user");
+ });
+
it("should apply object state", async () => {
const user = (await service.use(UserFactory).state({ role: "moderator" }).make()) as User;
expect(user.role).toBe("moderator");
@@ -205,4 +211,43 @@ describe("FactoryBuilder", () => {
expect(callbackEntity!.name).toBe(user.name);
});
});
+
+ describe("factory afterMake hook", () => {
+ it("should call factory afterMake when defined", async () => {
+ const user = (await service.use(UserFactoryWithHooks).make()) as User;
+ expect(user.name).toMatch(/^\[made\] /);
+ });
+ });
+
+ describe("factory afterCreate hook", () => {
+ it("should call factory afterCreate when defined", async () => {
+ const user = (await service.use(UserFactoryWithHooks).create()) as User;
+ expect(user.name).toMatch(/^\[created\] /);
+ });
+ });
+
+ describe("create() without DataSource", () => {
+ it("should throw when DataSource is not available", async () => {
+ const noDbModule = await Test.createTestingModule({
+ providers: [
+ {
+ provide: FACTORY_OPTIONS,
+ useValue: {
+ factories: [UserFactory],
+ },
+ },
+ FactoryService,
+ ],
+ }).compile();
+
+ await noDbModule.init();
+ const noDbService = noDbModule.get(FactoryService);
+
+ await expect(noDbService.use(UserFactory).create()).rejects.toThrow(
+ "DataSource is not available",
+ );
+
+ await noDbModule.close();
+ });
+ });
});
diff --git a/test/factory.service.spec.ts b/test/factory.service.spec.ts
index f3b6c2f..9d8b603 100644
--- a/test/factory.service.spec.ts
+++ b/test/factory.service.spec.ts
@@ -6,8 +6,9 @@ import { DataSource } from "typeorm";
import { FactoryService } from "../src/factory.service";
import { FactoryBuilder } from "../src/factory-builder";
import { FactoryNotRegisteredException } from "../src/exceptions/factory-not-registered.exception";
+import { FactoryNotInitializedException } from "../src/exceptions/factory-not-initialized.exception";
import { FACTORY_OPTIONS } from "../src/factory.constants";
-import { User, Post, Comment, UserFactory, PostFactory, CommentFactory } from "./helpers/test-entities";
+import { User, Post, Comment, UserFactory, PostFactory, CommentFactory, DatabaseSeeder } from "./helpers/test-entities";
describe("FactoryService", () => {
let module: TestingModule;
@@ -147,4 +148,61 @@ describe("FactoryService", () => {
module = undefined as any;
});
});
+
+ describe("FactoryNotInitializedException", () => {
+ it("should have correct message and name", () => {
+ const error = new FactoryNotInitializedException();
+ expect(error.message).toContain("FactoryService has not been initialized");
+ expect(error.name).toBe("FactoryNotInitializedException");
+ expect(error).toBeInstanceOf(Error);
+ });
+ });
+
+ describe("eventEmitter integration", () => {
+ it("should emit events when eventEmitter is provided", async () => {
+ const emitted: { event: string; payload: any }[] = [];
+ const mockEventEmitter = {
+ emit(event: string, payload: any) {
+ emitted.push({ event, payload });
+ return true;
+ },
+ };
+
+ const eventModule = await Test.createTestingModule({
+ imports: [
+ TypeOrmModule.forRoot({
+ type: "better-sqlite3",
+ database: ":memory:",
+ entities: [User, Post, Comment],
+ synchronize: true,
+ }),
+ ],
+ providers: [
+ {
+ provide: FACTORY_OPTIONS,
+ useValue: {
+ factories: [UserFactory, PostFactory, CommentFactory],
+ seeders: [DatabaseSeeder],
+ },
+ },
+ {
+ provide: "EventEmitter2",
+ useValue: mockEventEmitter,
+ },
+ FactoryService,
+ ],
+ }).compile();
+
+ await eventModule.init();
+ const eventService = eventModule.get(FactoryService);
+
+ await eventService.seed();
+
+ expect(emitted.length).toBeGreaterThan(0);
+ expect(emitted[0].event).toBe("factory.seed.all.started");
+ expect(emitted[emitted.length - 1].event).toBe("factory.seed.all.completed");
+
+ await eventModule.close();
+ });
+ });
});
diff --git a/test/helpers/test-entities.ts b/test/helpers/test-entities.ts
index 1ef829f..57e125b 100644
--- a/test/helpers/test-entities.ts
+++ b/test/helpers/test-entities.ts
@@ -109,6 +109,30 @@ export class CommentFactory extends BaseFactory {
}
}
+export class UserFactoryWithHooks extends BaseFactory {
+ get entity() {
+ return User;
+ }
+
+ definition(faker: Faker): Partial {
+ return {
+ name: faker.person.fullName(),
+ email: faker.internet.email(),
+ role: "user",
+ age: faker.number.int({ min: 18, max: 65 }),
+ active: true,
+ };
+ }
+
+ async afterMake(entity: User): Promise {
+ entity.name = `[made] ${entity.name}`;
+ }
+
+ async afterCreate(entity: User): Promise {
+ entity.name = `[created] ${entity.name}`;
+ }
+}
+
export class DatabaseSeeder implements Seeder {
order = 0;
diff --git a/test/seeder.spec.ts b/test/seeder.spec.ts
index 3d59060..b6b4e87 100644
--- a/test/seeder.spec.ts
+++ b/test/seeder.spec.ts
@@ -140,6 +140,84 @@ describe("Seeder", () => {
});
});
+ describe("seed() with no seeders", () => {
+ it("should complete without error when no seeders configured", async () => {
+ const noSeederModule = await Test.createTestingModule({
+ imports: [
+ TypeOrmModule.forRoot({
+ type: "better-sqlite3",
+ database: ":memory:",
+ entities: [User],
+ synchronize: true,
+ }),
+ ],
+ providers: [
+ {
+ provide: FACTORY_OPTIONS,
+ useValue: {
+ factories: [UserFactory],
+ },
+ },
+ FactoryService,
+ ],
+ }).compile();
+
+ await noSeederModule.init();
+ const noSeederService = noSeederModule.get(FactoryService);
+
+ await expect(noSeederService.seed()).resolves.toBeUndefined();
+
+ await noSeederModule.close();
+ });
+ });
+
+ describe("seeders without order property", () => {
+ it("should default order to 0", async () => {
+ const executionOrder: string[] = [];
+
+ class NoOrderSeederA implements Seeder {
+ async run() {
+ executionOrder.push("A");
+ }
+ }
+
+ class NoOrderSeederB implements Seeder {
+ async run() {
+ executionOrder.push("B");
+ }
+ }
+
+ const orderedModule = await Test.createTestingModule({
+ imports: [
+ TypeOrmModule.forRoot({
+ type: "better-sqlite3",
+ database: ":memory:",
+ entities: [User],
+ synchronize: true,
+ }),
+ ],
+ providers: [
+ {
+ provide: FACTORY_OPTIONS,
+ useValue: {
+ factories: [UserFactory],
+ seeders: [NoOrderSeederA, NoOrderSeederB],
+ },
+ },
+ FactoryService,
+ ],
+ }).compile();
+
+ await orderedModule.init();
+ const orderedService = orderedModule.get(FactoryService);
+
+ await orderedService.seed();
+ expect(executionOrder).toEqual(["A", "B"]);
+
+ await orderedModule.close();
+ });
+ });
+
describe("seeder creates expected data", () => {
it("should create users with expected fields", async () => {
await service.runSeeder(DatabaseSeeder);