From 6fafbbd10986b190d1d2c4955384773f467e6f5e Mon Sep 17 00:00:00 2001 From: sbriat Date: Wed, 28 Jun 2023 14:24:34 +0200 Subject: [PATCH] improve tests --- package.json | 2 + .../tests/unit/ddd/domain-event.base.spec.ts | 42 ++++ src/libs/tests/unit/ddd/entity.base.spec.ts | 209 ++++++++++++++++++ src/libs/tests/unit/ddd/query.base.spec.ts | 40 ++++ .../tests/unit/ddd/value-object.base.spec.ts | 42 ++++ 5 files changed, 335 insertions(+) create mode 100644 src/libs/tests/unit/ddd/domain-event.base.spec.ts create mode 100644 src/libs/tests/unit/ddd/entity.base.spec.ts create mode 100644 src/libs/tests/unit/ddd/query.base.spec.ts create mode 100644 src/libs/tests/unit/ddd/value-object.base.spec.ts diff --git a/package.json b/package.json index 46efac1..ad046cd 100644 --- a/package.json +++ b/package.json @@ -102,6 +102,7 @@ "libs/exceptions", "libs/types", "prisma.service.ts", + "convert-props-to-object.util.ts", "main.ts" ], "rootDir": "src", @@ -122,6 +123,7 @@ "libs/exceptions", "libs/types", "prisma.service.ts", + "convert-props-to-object.util.ts", "main.ts" ], "coverageDirectory": "../coverage", diff --git a/src/libs/tests/unit/ddd/domain-event.base.spec.ts b/src/libs/tests/unit/ddd/domain-event.base.spec.ts new file mode 100644 index 0000000..d4751b2 --- /dev/null +++ b/src/libs/tests/unit/ddd/domain-event.base.spec.ts @@ -0,0 +1,42 @@ +import { DomainEvent, DomainEventProps } from '@libs/ddd'; +import { ArgumentNotProvidedException } from '@libs/exceptions'; + +class FakeDomainEvent extends DomainEvent { + readonly name: string; + + constructor(props: DomainEventProps) { + super(props); + this.name = props.name; + } +} + +describe('DomainEvent Base', () => { + it('should define a domain event based object instance', () => { + const fakeDomainEvent = new FakeDomainEvent({ + aggregateId: 'some-id', + name: 'some-name', + }); + expect(fakeDomainEvent).toBeDefined(); + expect(fakeDomainEvent.id.length).toBe(36); + }); + + it('should define a domain event based object instance with metadata', () => { + const fakeDomainEvent = new FakeDomainEvent({ + aggregateId: 'some-id', + name: 'some-name', + metadata: { + correlationId: 'some-correlation-id', + causationId: 'some-causation-id', + userId: 'some-user-id', + timestamp: new Date('2023-06-28T05:00:00Z').getTime(), + }, + }); + expect(fakeDomainEvent.metadata.timestamp).toBe(1687928400000); + }); + it('should throw an exception if props are empty', () => { + const emptyProps: DomainEventProps = undefined; + expect(() => new FakeDomainEvent(emptyProps)).toThrow( + ArgumentNotProvidedException, + ); + }); +}); diff --git a/src/libs/tests/unit/ddd/entity.base.spec.ts b/src/libs/tests/unit/ddd/entity.base.spec.ts new file mode 100644 index 0000000..38f90f2 --- /dev/null +++ b/src/libs/tests/unit/ddd/entity.base.spec.ts @@ -0,0 +1,209 @@ +import { Entity } from '@libs/ddd'; +import { ArgumentOutOfRangeException } from '@libs/exceptions'; + +interface FakeProps { + name: string; +} + +class FakeEntity extends Entity { + protected _id: string; + + validate(): void { + // not implemented + } +} + +describe('Entity Base', () => { + it('should define an entity based object instance', () => { + const fakeInstance = new FakeEntity({ + id: 'some-id', + props: { + name: 'some-name', + }, + }); + expect(fakeInstance).toBeDefined(); + expect(fakeInstance.id).toBe('some-id'); + expect(fakeInstance.createdAt).toBeInstanceOf(Date); + expect(fakeInstance.updatedAt).toBeInstanceOf(Date); + expect(FakeEntity.isEntity(fakeInstance)).toBeTruthy(); + }); + + it('should define an entity with given created and updated dates', () => { + const fakeInstance = new FakeEntity({ + id: 'some-id', + createdAt: new Date('2023-06-28T05:00:00Z'), + updatedAt: new Date('2023-06-28T06:00:00Z'), + props: { + name: 'some-name', + }, + }); + expect(fakeInstance.createdAt.getUTCHours()).toBe(5); + expect(fakeInstance.updatedAt.getUTCHours()).toBe(6); + }); + + it('should compare entities', () => { + const fakeInstance = new FakeEntity({ + id: 'some-id', + props: { + name: 'some-name', + }, + }); + const fakeInstanceClone = new FakeEntity({ + id: 'some-id', + props: { + name: 'some-name', + }, + }); + const fakeInstanceNotReallyClone = new FakeEntity({ + id: 'some-slightly-different-id', + props: { + name: 'some-name', + }, + }); + const undefinedFakeInstance: FakeEntity = undefined; + expect(fakeInstance.equals(undefinedFakeInstance)).toBeFalsy(); + expect(fakeInstance.equals(fakeInstance)).toBeTruthy(); + expect(fakeInstance.equals(fakeInstanceClone)).toBeTruthy(); + expect(fakeInstance.equals(fakeInstanceNotReallyClone)).toBeFalsy(); + }); + + it('should convert entity to plain object', () => { + const fakeInstance = new FakeEntity({ + id: 'some-id', + createdAt: new Date('2023-06-28T05:00:00Z'), + updatedAt: new Date('2023-06-28T06:00:00Z'), + props: { + name: 'some-name', + }, + }); + expect(fakeInstance.toObject()).toEqual({ + id: 'some-id', + createdAt: new Date('2023-06-28T05:00:00.000Z'), + updatedAt: new Date('2023-06-28T06:00:00.000Z'), + name: 'some-name', + }); + }); + + it('should throw an exception if props number is too high', () => { + interface BigFakeProps { + prop1: string; + prop2: string; + prop3: string; + prop4: string; + prop5: string; + prop6: string; + prop7: string; + prop8: string; + prop9: string; + prop10: string; + prop11: string; + prop12: string; + prop13: string; + prop14: string; + prop15: string; + prop16: string; + prop17: string; + prop18: string; + prop19: string; + prop20: string; + prop21: string; + prop22: string; + prop23: string; + prop24: string; + prop25: string; + prop26: string; + prop27: string; + prop28: string; + prop29: string; + prop30: string; + prop31: string; + prop32: string; + prop33: string; + prop34: string; + prop35: string; + prop36: string; + prop37: string; + prop38: string; + prop39: string; + prop40: string; + prop41: string; + prop42: string; + prop43: string; + prop44: string; + prop45: string; + prop46: string; + prop47: string; + prop48: string; + prop49: string; + prop50: string; + prop51: string; + } + + class BigFakeEntity extends Entity { + protected _id: string; + + validate(): void { + // not implemented + } + } + expect( + () => + new BigFakeEntity({ + id: 'some-id', + props: { + prop1: 'some-name', + prop2: 'some-name', + prop3: 'some-name', + prop4: 'some-name', + prop5: 'some-name', + prop6: 'some-name', + prop7: 'some-name', + prop8: 'some-name', + prop9: 'some-name', + prop10: 'some-name', + prop11: 'some-name', + prop12: 'some-name', + prop13: 'some-name', + prop14: 'some-name', + prop15: 'some-name', + prop16: 'some-name', + prop17: 'some-name', + prop18: 'some-name', + prop19: 'some-name', + prop20: 'some-name', + prop21: 'some-name', + prop22: 'some-name', + prop23: 'some-name', + prop24: 'some-name', + prop25: 'some-name', + prop26: 'some-name', + prop27: 'some-name', + prop28: 'some-name', + prop29: 'some-name', + prop30: 'some-name', + prop31: 'some-name', + prop32: 'some-name', + prop33: 'some-name', + prop34: 'some-name', + prop35: 'some-name', + prop36: 'some-name', + prop37: 'some-name', + prop38: 'some-name', + prop39: 'some-name', + prop40: 'some-name', + prop41: 'some-name', + prop42: 'some-name', + prop43: 'some-name', + prop44: 'some-name', + prop45: 'some-name', + prop46: 'some-name', + prop47: 'some-name', + prop48: 'some-name', + prop49: 'some-name', + prop50: 'some-name', + prop51: 'some-name', + }, + }), + ).toThrow(ArgumentOutOfRangeException); + }); +}); diff --git a/src/libs/tests/unit/ddd/query.base.spec.ts b/src/libs/tests/unit/ddd/query.base.spec.ts new file mode 100644 index 0000000..9d93d8f --- /dev/null +++ b/src/libs/tests/unit/ddd/query.base.spec.ts @@ -0,0 +1,40 @@ +import { + PaginatedParams, + PaginatedQueryBase, + QueryBase, +} from '@libs/ddd/query.base'; + +class FakeQuery extends QueryBase { + readonly id: string; + + constructor(id: string) { + super(); + this.id = id; + } +} + +describe('Query Base', () => { + it('should define a query based object instance', () => { + const fakeQuery = new FakeQuery('some-id'); + expect(fakeQuery).toBeDefined(); + }); +}); + +class FakePaginatedQuery extends PaginatedQueryBase { + readonly id: string; + + constructor(props: PaginatedParams) { + super(props); + this.id = props.id; + } +} + +describe('Paginated Query Base', () => { + it('should define a paginated query based object instance', () => { + const fakePaginatedQuery = new FakePaginatedQuery({ + id: 'some-id', + page: 1, + }); + expect(fakePaginatedQuery).toBeDefined(); + }); +}); diff --git a/src/libs/tests/unit/ddd/value-object.base.spec.ts b/src/libs/tests/unit/ddd/value-object.base.spec.ts new file mode 100644 index 0000000..2b99005 --- /dev/null +++ b/src/libs/tests/unit/ddd/value-object.base.spec.ts @@ -0,0 +1,42 @@ +import { ValueObject } from '@libs/ddd'; + +interface FakeProps { + name: string; +} + +class FakeValueObject extends ValueObject { + get name(): string { + return this.props.name; + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + protected validate(props: FakeProps): void { + return; + } +} + +describe('Value Object Base', () => { + it('should create a base value object', () => { + const fakeValueObject = new FakeValueObject({ name: 'fakeName' }); + expect(fakeValueObject).toBeDefined(); + expect(ValueObject.isValueObject(fakeValueObject)).toBeTruthy(); + }); + + it('should compare value objects', () => { + const fakeValueObject = new FakeValueObject({ name: 'fakeName' }); + const fakeValueObjectClone = new FakeValueObject({ name: 'fakeName' }); + const undefinedFakeValueObject: FakeValueObject = undefined; + const nullFakeValueObject: FakeValueObject = null; + expect(fakeValueObject.equals(undefinedFakeValueObject)).toBeFalsy(); + expect(fakeValueObject.equals(nullFakeValueObject)).toBeFalsy(); + expect(fakeValueObject.equals(fakeValueObject)).toBeTruthy(); + expect(fakeValueObject.equals(fakeValueObjectClone)).toBeTruthy(); + }); + + it('should unpack value object props', () => { + const fakeValueObject = new FakeValueObject({ name: 'fakeName' }); + expect(fakeValueObject.unpack()).toEqual({ + name: 'fakeName', + }); + }); +});