;
+
+ const fakerMock = ({
+ random: {
+ alpha: jest.fn(),
+ number: jest.fn(),
+ boolean: jest.fn(),
+ alphaNumeric: jest.fn(),
+ },
+ date: {
+ recent: jest.fn(),
+ },
+ } as unknown) as FakerStatic;
+
+ describe('given a ArrayValueHandler', () => {
+ beforeAll(() => {
+ handler = new ArrayValueHandler(fakerMock, classProcessorMock);
+ });
+
+ describe("when calling 'shouldHandle' with type 'object' and value of multi class ({ type: ClassType })", () => {
+ test('then return true', () => {
+ expect(handler.shouldHandle(dto)).toBeTruthy();
+ });
+ });
+
+ describe("when calling 'produceValue' method", () => {
+ describe('and the value is null', () => {
+ test('return null', () => {
+ dto.value = null;
+ expect(handler.produceValue(dto)).toBeNull();
+ });
+ });
+
+ describe('and value.type (class type) is Primitive', () => {
+ describe('and the primitive value is String', () => {
+ let result;
+
+ beforeAll(() => {
+ dto.value = { type: String, count: DEFAULT_COUNT_FOR_DTO };
+ result = handler.produceValue(dto);
+ });
+
+ test('then call random alpha string from faker', () => {
+ expect(fakerMock.random.alpha).toHaveBeenCalledTimes(DEFAULT_COUNT_FOR_DTO);
+ });
+
+ test("then return an array of 'count' strings", () => {
+ expect(result).toBeInstanceOf(Array);
+ expect(result).toHaveLength(DEFAULT_COUNT_FOR_DTO);
+ });
+ });
+ });
+
+ describe('and the primitive value is Number', () => {
+ let result;
+
+ beforeAll(() => {
+ dto.value = { type: Number, count: DEFAULT_COUNT_FOR_DTO };
+ result = handler.produceValue(dto);
+ });
+
+ test('then call random alpha string from faker', () => {
+ expect(fakerMock.random.number).toHaveBeenCalledTimes(DEFAULT_COUNT_FOR_DTO);
+ expect(fakerMock.random.number).toHaveBeenCalledWith(1000);
+ });
+
+ test("then return an array of 'count' numbers", () => {
+ expect(result).toBeInstanceOf(Array);
+ expect(result).toHaveLength(DEFAULT_COUNT_FOR_DTO);
+ });
+ });
+
+ describe('and the primitive value is Boolean', () => {
+ test('and the primitive value is Boolean', () => {
+ dto.value = { type: Boolean, count: DEFAULT_COUNT_FOR_DTO };
+
+ handler.produceValue(dto);
+ expect(fakerMock.random.boolean).toHaveBeenCalledTimes(DEFAULT_COUNT_FOR_DTO);
+ });
+ });
+
+ describe('and the primitive value is Date', () => {
+ test('and the primitive value is Date', () => {
+ dto.value = { type: Date, count: DEFAULT_COUNT_FOR_DTO };
+
+ handler.produceValue(dto);
+ expect(fakerMock.date.recent).toHaveBeenCalledTimes(DEFAULT_COUNT_FOR_DTO);
+ });
+ });
+ });
+
+ describe('and value type is an actual class (none a primitive)', () => {
+ test("then call 'process' with 'count' times", () => {
+ dto.value = { type: DTO_CLASS_VALUE, count: DEFAULT_COUNT_FOR_DTO };
+
+ handler.produceValue(dto);
+ expect(classProcessorMock.process).toHaveBeenCalledTimes(DEFAULT_COUNT_FOR_DTO);
+ });
+ });
+ });
+});
diff --git a/src/handlers/array-value-handler.ts b/src/handlers/array-value-handler.ts
new file mode 100644
index 0000000..7bea16b
--- /dev/null
+++ b/src/handlers/array-value-handler.ts
@@ -0,0 +1,53 @@
+import { ValueHandler } from '../types/value-handler.interface';
+import { PropertyDto } from '../types/property-dto.interface';
+import { ExactValue, Class } from '../types/fixture-options.type';
+import { MultiClass } from '../types/fixture-options.type';
+import { ClassProcessor } from '../class-processor';
+import { PrimitiveHandlerAbstract } from './primitive-handler-abstract';
+
+import FakerStatic = Faker.FakerStatic;
+
+// TODO: refactor (2nd phase). All other fixture options should be wrapped with 'multiple' functionality
+export class ArrayValueHandler extends PrimitiveHandlerAbstract
implements ValueHandler
{
+ public constructor(protected readonly faker: FakerStatic, protected readonly classProcessor: ClassProcessor) {
+ super(faker);
+ }
+
+ public static hasTypeKey(propertyDto: PropertyDto): boolean {
+ const { value } = propertyDto;
+
+ return Object.prototype.hasOwnProperty.call(value, 'type');
+ }
+
+ public shouldHandle(propertyDto: PropertyDto): boolean {
+ return propertyDto.type === 'object' && ArrayValueHandler.hasTypeKey(propertyDto);
+ }
+
+ public produceValue(propertyDto: PropertyDto): any {
+ const { value } = propertyDto;
+
+ if (value === null) {
+ return value;
+ }
+
+ const { count, type } = value;
+
+ if (PrimitiveHandlerAbstract.PRIMITIVES.includes(type.name)) {
+ const instances = new Array(count);
+
+ for (let index = 0; index < count; index++) {
+ instances[index] = super.generateRandomValueFromPrimitive(type.name);
+ }
+
+ return instances;
+ }
+
+ const instances = new Array(count);
+
+ for (let index = 0; index < count; index++) {
+ instances[index] = this.classProcessor.process(type as Class);
+ }
+
+ return instances;
+ }
+}
diff --git a/src/handlers/callback-value-handler.test.ts b/src/handlers/callback-value-handler.test.ts
new file mode 100644
index 0000000..8e08f02
--- /dev/null
+++ b/src/handlers/callback-value-handler.test.ts
@@ -0,0 +1,46 @@
+import { CallbackValueHandler } from '../handlers/callback-value-handler';
+import { Callback } from '../types/fixture-options.type';
+
+import FakerStatic = Faker.FakerStatic;
+import { PropertyDto } from 'src/types/property-dto.interface';
+
+describe('CallbackValueInspector Unit', () => {
+ let dto: PropertyDto, handler: CallbackValueHandler;
+
+ const fakerMock = ({ internet: { email: jest.fn() } } as unknown) as FakerStatic;
+
+ beforeEach(() => {
+ dto = {
+ type: 'function',
+ // eslint-disable-next-line @typescript-eslint/ban-ts-ignore
+ // @ts-ignore
+ value: { name: '' },
+ name: 'testPropertyName',
+ constructorName: '',
+ };
+ });
+
+ describe('given a CallbackValueInspector', () => {
+ beforeAll(() => {
+ handler = new CallbackValueHandler(fakerMock);
+ });
+
+ describe("when calling 'shouldHandle' method with type function name and empty constructor name", () => {
+ test('then return true', () => {
+ const result = handler.shouldHandle(dto);
+
+ expect(result).toBeTruthy();
+ });
+ });
+
+ describe("when calling 'produceValue' ", () => {
+ test('then call the callback function with same faker instance', () => {
+ dto.value = jest.fn();
+ handler.produceValue(dto);
+
+ expect(dto.value).toHaveBeenCalledTimes(1);
+ expect(dto.value).toHaveBeenCalledWith(fakerMock);
+ });
+ });
+ });
+});
diff --git a/src/handlers/callback-value-handler.ts b/src/handlers/callback-value-handler.ts
new file mode 100644
index 0000000..3b9b0de
--- /dev/null
+++ b/src/handlers/callback-value-handler.ts
@@ -0,0 +1,19 @@
+import { ValueHandler } from '../types/value-handler.interface';
+import { PropertyDto } from '../types/property-dto.interface';
+import { Callback } from '../types/fixture-options.type';
+
+import FakerStatic = Faker.FakerStatic;
+
+export class CallbackValueHandler implements ValueHandler
{
+ protected static readonly PRIMITIVES = ['String', 'Boolean', 'Number', 'Date'];
+
+ public constructor(protected readonly faker: FakerStatic) {}
+
+ public shouldHandle(propertyDto: PropertyDto
): boolean {
+ return propertyDto.type === 'function' && propertyDto.value.name === '';
+ }
+
+ public produceValue(propertyDto: PropertyDto): any {
+ return propertyDto.value(this.faker);
+ }
+}
diff --git a/src/handlers/enum-value-handler.test.ts b/src/handlers/enum-value-handler.test.ts
new file mode 100644
index 0000000..facf5ac
--- /dev/null
+++ b/src/handlers/enum-value-handler.test.ts
@@ -0,0 +1,48 @@
+import { EnumValueHandler } from '../handlers/enum-value-handler';
+import { EnumObject } from '../types/fixture-options.type';
+
+import FakerStatic = Faker.FakerStatic;
+
+describe('EnumValueInspector Unit', () => {
+ enum TestEnum {
+ StateOne = 'one',
+ StateTwo = 'two',
+ StateThree = 'three',
+ }
+
+ const fakerMock = ({
+ random: {
+ arrayElement: jest.fn(),
+ },
+ } as unknown) as FakerStatic;
+
+ let dto, handler: EnumValueHandler;
+
+ describe('given a EnumValueInspector', () => {
+ beforeAll(() => {
+ handler = new EnumValueHandler(fakerMock);
+
+ dto = {
+ type: 'object',
+ value: { enum: TestEnum },
+ name: 'testPropertyName',
+ };
+ });
+
+ describe("when calling 'shouldHandle' method with type object and { type: enum }", () => {
+ test('then return true', () => {
+ expect(handler.shouldHandle(dto)).toBeTruthy();
+ });
+ });
+
+ describe("when calling 'produceValue' method", () => {
+ test('then call faker random array element', () => {
+ dto.value = { enum: TestEnum };
+ handler.produceValue(dto);
+
+ expect(fakerMock.random.arrayElement).toHaveBeenCalledTimes(1);
+ expect(fakerMock.random.arrayElement).toHaveBeenCalledWith(['one', 'two', 'three']);
+ });
+ });
+ });
+});
diff --git a/src/handlers/enum-value-handler.ts b/src/handlers/enum-value-handler.ts
new file mode 100644
index 0000000..adca6f7
--- /dev/null
+++ b/src/handlers/enum-value-handler.ts
@@ -0,0 +1,42 @@
+import { ValueHandler } from '../types/value-handler.interface';
+import { PropertyDto } from '../types/property-dto.interface';
+import { EnumObject } from '../types/fixture-options.type';
+
+import FakerStatic = Faker.FakerStatic;
+
+export class EnumValueHandler implements ValueHandler
{
+ public constructor(protected readonly faker: FakerStatic) {}
+
+ private static getEnumValues(enumObj: object): any[] {
+ const keysList = Object.getOwnPropertyNames(enumObj).filter(
+ (key) => enumObj.propertyIsEnumerable(key) && key !== String(parseFloat(key))
+ );
+
+ const length = keysList.length;
+ const valuesList = new Array(length);
+
+ for (let index = 0; index < length; ++index) {
+ const key = keysList[index];
+ valuesList[index] = enumObj[key];
+ }
+
+ return valuesList;
+ }
+
+ public static isEnumValue(propertyDto: PropertyDto): boolean {
+ const { value } = propertyDto;
+
+ return Object.prototype.hasOwnProperty.call(value, 'enum');
+ }
+
+ public shouldHandle(propertyDto: PropertyDto): boolean {
+ return propertyDto.type === 'object' && EnumValueHandler.isEnumValue(propertyDto);
+ }
+
+ public produceValue(propertyDto: PropertyDto): any {
+ const { value } = propertyDto;
+ const { enum: enumObj } = value as { enum: object };
+
+ return this.faker.random.arrayElement(EnumValueHandler.getEnumValues(enumObj));
+ }
+}
diff --git a/src/handlers/object-literal-value-handler.test.ts b/src/handlers/object-literal-value-handler.test.ts
new file mode 100644
index 0000000..f502010
--- /dev/null
+++ b/src/handlers/object-literal-value-handler.test.ts
@@ -0,0 +1,32 @@
+import { ObjectLiteralValueHandler } from '../handlers/object-literal-value-handler';
+import { ObjectLiteral } from '../types/fixture-options.type';
+
+describe('ObjectLiteralValueInspector Unit', () => {
+ let dto, handler: ObjectLiteralValueHandler;
+
+ const OBJECT_LITERAL_VALUE = { testArbitrary: 'and-arbitrary-value' };
+
+ describe('given a ObjectLiteralValueInspector', () => {
+ beforeAll(() => {
+ handler = new ObjectLiteralValueHandler();
+
+ dto = {
+ type: 'object',
+ value: OBJECT_LITERAL_VALUE,
+ name: 'testPropertyName',
+ };
+ });
+
+ describe("when calling 'shouldHandle' method with object literal", () => {
+ test('then return true', () => {
+ expect(handler.shouldHandle(dto)).toBeTruthy();
+ });
+ });
+
+ describe("when calling 'produceValue' method", () => {
+ test('return the exact same object literal', () => {
+ expect(handler.produceValue(dto)).toBe(OBJECT_LITERAL_VALUE);
+ });
+ });
+ });
+});
diff --git a/src/handlers/object-literal-value-handler.ts b/src/handlers/object-literal-value-handler.ts
new file mode 100644
index 0000000..ede99b7
--- /dev/null
+++ b/src/handlers/object-literal-value-handler.ts
@@ -0,0 +1,19 @@
+import { ArrayValueHandler } from './array-value-handler';
+import { EnumValueHandler } from './enum-value-handler';
+import { ValueHandler } from '../types/value-handler.interface';
+import { PropertyDto } from '../types/property-dto.interface';
+import { EnumObject, MultiClass, ObjectLiteral } from '../types/fixture-options.type';
+
+export class ObjectLiteralValueHandler implements ValueHandler
{
+ public shouldHandle(propertyDto: PropertyDto
): boolean {
+ return (
+ propertyDto.type === 'object' &&
+ !ArrayValueHandler.hasTypeKey((propertyDto as unknown) as PropertyDto) &&
+ !EnumValueHandler.isEnumValue((propertyDto as unknown) as PropertyDto)
+ );
+ }
+
+ public produceValue(propertyDto: PropertyDto): any {
+ return propertyDto.value;
+ }
+}
diff --git a/src/handlers/primitive-handler-abstract.ts b/src/handlers/primitive-handler-abstract.ts
new file mode 100644
index 0000000..dbab816
--- /dev/null
+++ b/src/handlers/primitive-handler-abstract.ts
@@ -0,0 +1,34 @@
+import { PropertyDto } from '../types/property-dto.interface';
+import { ExactValue, FixtureOptions } from '../types/fixture-options.type';
+
+import FakerStatic = Faker.FakerStatic;
+
+export abstract class PrimitiveHandlerAbstract
{
+ public static readonly PRIMITIVES = ['String', 'Boolean', 'Number', 'Date'];
+
+ protected constructor(protected readonly faker: FakerStatic) {}
+
+ protected generateRandomValueFromPrimitive(ctor: string): ExactValue {
+ const { faker } = this;
+
+ if (ctor === 'String') {
+ return faker.random.alpha({ count: 10 });
+ } else if (ctor === 'Number') {
+ return faker.random.number(1000);
+ } else if (ctor === 'Boolean') {
+ return faker.random.boolean();
+ } else if (ctor === 'Date') {
+ return faker.date.recent();
+ } else {
+ return faker.random.alphaNumeric();
+ }
+ }
+
+ public isConstructorNamePrimitive(propertyDto: PropertyDto
) {
+ return PrimitiveHandlerAbstract.PRIMITIVES.includes(propertyDto.constructorName);
+ }
+
+ public isPrimitive(propertyDto: PropertyDto
): boolean {
+ return this.isConstructorNamePrimitive(propertyDto) && propertyDto.type !== 'function';
+ }
+}
diff --git a/src/handlers/primitive-value-handler.test.ts b/src/handlers/primitive-value-handler.test.ts
new file mode 100644
index 0000000..ef19e81
--- /dev/null
+++ b/src/handlers/primitive-value-handler.test.ts
@@ -0,0 +1,139 @@
+import { PrimitiveValueHandler } from './primitive-value-handler';
+import { PropertyDto } from '../types/property-dto.interface';
+import { ExactValue } from '../types/fixture-options.type';
+
+import FakerStatic = Faker.FakerStatic;
+
+describe('PrimitiveValueInspector Unit', () => {
+ let dto, handler: PrimitiveValueHandler;
+
+ const fakerMock = ({
+ random: {
+ alpha: jest.fn(),
+ number: jest.fn(),
+ boolean: jest.fn(),
+ alphaNumeric: jest.fn(),
+ },
+ date: {
+ recent: jest.fn(),
+ },
+ setLocale: () => jest.fn(),
+ } as unknown) as FakerStatic;
+
+ describe('given a PrimitiveValueInspector', () => {
+ beforeAll(() => {
+ handler = new PrimitiveValueHandler(fakerMock);
+ });
+
+ describe("when calling 'shouldHandle' method", () => {
+ const dto: PropertyDto = {
+ name: 'some-prop-name',
+ value: undefined,
+ type: 'not-a-function',
+ constructorName: null,
+ };
+
+ describe('and the property type is not a function', () => {
+ test('then return true when constructor name is a String', () => {
+ dto.constructorName = 'String';
+ expect(handler.shouldHandle(dto)).toBeTruthy();
+ });
+
+ test('then return true when constructor name is a Number', () => {
+ dto.constructorName = 'Number';
+ expect(handler.shouldHandle(dto)).toBeTruthy();
+ });
+
+ test('then return true when constructor name is a Boolean', () => {
+ dto.constructorName = 'Boolean';
+ expect(handler.shouldHandle(dto)).toBeTruthy();
+ });
+
+ test('then return true when constructor name is a Date', () => {
+ dto.constructorName = 'Date';
+ expect(handler.shouldHandle(dto)).toBeTruthy();
+ });
+ });
+ });
+
+ describe("when calling 'produceValue' method", () => {
+ beforeEach(() => {
+ dto = { type: 'string', value: 'TestStr', name: 'name' };
+ });
+
+ describe('and there is a value', () => {
+ test('then return the exact same value', () => {
+ dto.value = 'TestStr';
+ expect(handler.produceValue(dto)).toBe('TestStr');
+
+ dto.value = 12345;
+ expect(handler.produceValue(dto)).toBe(12345);
+
+ dto.value = true;
+ expect(handler.produceValue(dto)).toBe(true);
+ });
+ });
+
+ describe('and the value is including { type } inside (multi class)', () => {
+ test('then throw an error about type mismatch', () => {
+ dto.value = { type: String, count: 3 };
+ expect(() => handler.produceValue(dto)).toThrowError(Error);
+ });
+ });
+
+ describe('and there is no value (empty value)', () => {
+ beforeEach(() => {
+ dto.value = undefined;
+ });
+
+ describe('and the constructor is a String', () => {
+ test('then generate a random string from faker', () => {
+ dto.constructorName = 'String';
+
+ handler.produceValue(dto);
+
+ expect(fakerMock.random.alpha).toHaveBeenCalledTimes(1);
+ });
+ });
+
+ describe('and the constructor is a Number', () => {
+ test('then return a random number between 1 to 1000 from faker', () => {
+ dto.constructorName = 'Number';
+
+ handler.produceValue(dto);
+
+ expect(fakerMock.random.number).toHaveBeenCalledTimes(1);
+ expect(fakerMock.random.number).toHaveBeenCalledWith(1000);
+ });
+ });
+
+ describe('and the constructor is a Boolean', () => {
+ test('then return random boolean value', () => {
+ dto.constructorName = 'Boolean';
+
+ handler.produceValue(dto);
+ expect(fakerMock.random.boolean).toHaveBeenCalledTimes(1);
+ });
+ });
+
+ describe('and the constructor is a Date', () => {
+ test('then return a random date', () => {
+ dto.constructorName = 'Date';
+
+ handler.produceValue(dto);
+ expect(fakerMock.date.recent).toHaveBeenCalledTimes(1);
+ });
+ });
+
+ describe('and constructor is not a primitive one', () => {
+ test('then return alpha numeric string', () => {
+ dto.constructorName = 'not-a-primitive';
+
+ handler.produceValue(dto);
+ expect(fakerMock.random.alphaNumeric).toHaveBeenCalledTimes(1);
+ });
+ });
+ });
+ });
+ });
+});
diff --git a/src/handlers/primitive-value-handler.ts b/src/handlers/primitive-value-handler.ts
new file mode 100644
index 0000000..3f461e6
--- /dev/null
+++ b/src/handlers/primitive-value-handler.ts
@@ -0,0 +1,35 @@
+import { PrimitiveHandlerAbstract } from './primitive-handler-abstract';
+import { ValueHandler } from '../types/value-handler.interface';
+import { PropertyDto } from '../types/property-dto.interface';
+import { ExactValue, MultiClass } from '../types/fixture-options.type';
+import { ArrayValueHandler } from './array-value-handler';
+
+import FakerStatic = Faker.FakerStatic;
+
+export class PrimitiveValueHandler
+ extends PrimitiveHandlerAbstract
+ implements ValueHandler
{
+ public constructor(protected readonly faker: FakerStatic) {
+ super(faker);
+ }
+
+ public shouldHandle(propertyDto: PropertyDto
): boolean {
+ return this.isPrimitive(propertyDto);
+ }
+
+ public produceValue(propertyDto: PropertyDto): any {
+ const { value } = propertyDto;
+
+ if (typeof value !== 'undefined') {
+ if (ArrayValueHandler.hasTypeKey((propertyDto as unknown) as PropertyDto)) {
+ throw new Error(
+ 'Type mismatch. Properties decorated with @Fixture({ type: ClassType }) must be typed as array (e.g. prop: string[])'
+ );
+ }
+
+ return value;
+ }
+
+ return super.generateRandomValueFromPrimitive(propertyDto.constructorName);
+ }
+}
diff --git a/src/handlers/single-class-value-handler.test.ts b/src/handlers/single-class-value-handler.test.ts
new file mode 100644
index 0000000..abe9c6e
--- /dev/null
+++ b/src/handlers/single-class-value-handler.test.ts
@@ -0,0 +1,43 @@
+import { PropertyDto } from '../types/property-dto.interface';
+import { SingleClassValueHandler } from '../handlers/single-class-value-handler';
+import { ClassProcessor } from '../class-processor';
+
+import FakerStatic = Faker.FakerStatic;
+import { Class } from 'src/types';
+
+describe('SingleClassValueInspector Unit', () => {
+ let handler: SingleClassValueHandler;
+ const DTO_CLASS_VALUE = class TestClass {};
+
+ const dto: PropertyDto = {
+ type: 'function',
+ value: DTO_CLASS_VALUE,
+ name: 'testPropertyName',
+ constructorName: 'TestClass',
+ };
+
+ const classProcessorMock = ({
+ process: jest.fn(),
+ } as unknown) as ClassProcessor;
+
+ describe('given a SingleClassValueInspector', () => {
+ beforeAll(() => {
+ handler = new SingleClassValueHandler({} as FakerStatic, classProcessorMock);
+ });
+
+ describe("when calling 'shouldHandle' with a none-primitive, 'function' type", () => {
+ test('then return true', () => {
+ expect(handler.shouldHandle(dto)).toBeTruthy();
+ });
+ });
+
+ describe("when calling 'produceValue' method", () => {
+ test("then call 'process' with the given class", () => {
+ handler.produceValue(dto);
+
+ expect(classProcessorMock.process).toHaveBeenCalledTimes(1);
+ expect(classProcessorMock.process).toHaveBeenCalledWith(DTO_CLASS_VALUE);
+ });
+ });
+ });
+});
diff --git a/src/handlers/single-class-value-handler.ts b/src/handlers/single-class-value-handler.ts
new file mode 100644
index 0000000..edebba7
--- /dev/null
+++ b/src/handlers/single-class-value-handler.ts
@@ -0,0 +1,21 @@
+import { PrimitiveHandlerAbstract } from './primitive-handler-abstract';
+import { ClassProcessor } from '../class-processor';
+import { ValueHandler } from '../types/value-handler.interface';
+import { PropertyDto } from '../types/property-dto.interface';
+import { Class } from '../types/fixture-options.type';
+
+import FakerStatic = Faker.FakerStatic;
+
+export class SingleClassValueHandler extends PrimitiveHandlerAbstract
implements ValueHandler
{
+ public constructor(protected readonly faker: FakerStatic, protected readonly classProcessor: ClassProcessor) {
+ super(faker);
+ }
+
+ public shouldHandle(propertyDto: PropertyDto): boolean {
+ return propertyDto.type === 'function' && !this.isConstructorNamePrimitive(propertyDto);
+ }
+
+ public produceValue(propertyDto: PropertyDto): any {
+ return this.classProcessor.process(propertyDto.value);
+ }
+}
diff --git a/src/index.ts b/src/index.ts
new file mode 100644
index 0000000..3bf9ba3
--- /dev/null
+++ b/src/index.ts
@@ -0,0 +1,3 @@
+export * from './types';
+export * from './decorators';
+export * from './factories';
diff --git a/src/types/class-reflection-dto.type.ts b/src/types/class-reflection-dto.type.ts
new file mode 100644
index 0000000..2aca3d6
--- /dev/null
+++ b/src/types/class-reflection-dto.type.ts
@@ -0,0 +1,4 @@
+import { PropertyDto } from './property-dto.interface';
+import { FixtureOptions } from '../types/fixture-options.type';
+
+export type ClassReflectionDto = PropertyDto[];
diff --git a/src/types/fixture-factory-options.interface.ts b/src/types/fixture-factory-options.interface.ts
new file mode 100644
index 0000000..39df42a
--- /dev/null
+++ b/src/types/fixture-factory-options.interface.ts
@@ -0,0 +1,6 @@
+import FakerStatic = Faker.FakerStatic;
+
+export interface FixtureFactoryOptions {
+ count: number;
+ locale?: FakerStatic['locale'];
+}
diff --git a/src/types/fixture-options.type.ts b/src/types/fixture-options.type.ts
new file mode 100644
index 0000000..f0264e0
--- /dev/null
+++ b/src/types/fixture-options.type.ts
@@ -0,0 +1,16 @@
+export interface ObjectLiteral {
+ [key: string]: unknown;
+}
+
+export type ExactValue = string | number | boolean | ObjectLiteral | Date;
+export type MultiClass = { type: Class; count: number };
+export type EnumObject = { enum: object };
+export type Callback = (faker: Faker.FakerStatic) => any;
+
+export interface Class extends Function {
+ new (...args: any[]): T;
+}
+
+export type ClassLiteral = Partial<{ [K in keyof T]: T[K] }>;
+
+export type FixtureOptions = Callback | ExactValue | Class | EnumObject | MultiClass;
diff --git a/src/types/iclass-processor.interface.ts b/src/types/iclass-processor.interface.ts
new file mode 100644
index 0000000..ce734d2
--- /dev/null
+++ b/src/types/iclass-processor.interface.ts
@@ -0,0 +1,6 @@
+import { ClassLiteral, Class } from './fixture-options.type';
+
+// eslint-disable-next-line @typescript-eslint/interface-name-prefix
+export interface IClassProcessor {
+ process(target: Class): ClassLiteral;
+}
diff --git a/src/types/index.ts b/src/types/index.ts
new file mode 100644
index 0000000..bb86a3d
--- /dev/null
+++ b/src/types/index.ts
@@ -0,0 +1,2 @@
+export * from './fixture-options.type';
+export * from './fixture-factory-options.interface';
diff --git a/src/types/property-dto.interface.ts b/src/types/property-dto.interface.ts
new file mode 100644
index 0000000..6d4bb58
--- /dev/null
+++ b/src/types/property-dto.interface.ts
@@ -0,0 +1,10 @@
+import { FixtureOptions } from './fixture-options.type';
+
+// TODO: Make generic
+// TODO: refactor (2nd phase). change to class, which will contain all relevant methods which are now speared as static functions in different classes
+export interface PropertyDto {
+ value: T;
+ name: string;
+ constructorName: string;
+ type: string;
+}
diff --git a/src/types/value-handler.interface.ts b/src/types/value-handler.interface.ts
new file mode 100644
index 0000000..6235ea2
--- /dev/null
+++ b/src/types/value-handler.interface.ts
@@ -0,0 +1,9 @@
+import { PropertyDto } from './property-dto.interface';
+import { IClassProcessor } from '../types/iclass-processor.interface';
+import { FixtureOptions } from '../types/fixture-options.type';
+
+export interface ValueHandler {
+ shouldHandle(propertyDto: PropertyDto
): boolean;
+
+ produceValue(propertyDto: PropertyDto, classProcessor?: IClassProcessor): T | T[];
+}
diff --git a/test/fixture-factory-circular.ts b/test/fixture-factory-circular.ts
new file mode 100644
index 0000000..cb1df96
--- /dev/null
+++ b/test/fixture-factory-circular.ts
@@ -0,0 +1,30 @@
+import { Fixture } from '../src/decorators/fixture.decorator';
+import { FixtureFactory } from '../src/factories/fixture-factory';
+
+describe('Fixture Factory - circular class-type', () => {
+ describe('with single class circular fixture', () => {
+ class Man {
+ @Fixture(Man)
+ readonly son: Man;
+ }
+
+ test('when calling FixtureFactory.create it throws an exception', () => {
+ expect(() => FixtureFactory.create(Man)).toThrowError(
+ 'Circular class-type fixture detected! Target: Man; Property: son'
+ );
+ });
+ });
+
+ describe('with multiple class circular fixture', () => {
+ class AnotherMan {
+ @Fixture({ type: AnotherMan, count: 3 })
+ readonly sons: AnotherMan[];
+ }
+
+ test('When calling FixtureFactory.create it throws an exception', () => {
+ expect(() => FixtureFactory.create(AnotherMan)).toThrowError(
+ 'Circular class-type fixture detected! Target: AnotherMan; Property: sons'
+ );
+ });
+ });
+});
diff --git a/test/integration/__snapshots__/fixture-decorator.test.ts.snap b/test/integration/__snapshots__/fixture-decorator.test.ts.snap
new file mode 100644
index 0000000..e3f3e3a
--- /dev/null
+++ b/test/integration/__snapshots__/fixture-decorator.test.ts.snap
@@ -0,0 +1,24 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Fixture Factory - Integration Test Given a decorated class when using the related decorator with absolute values then return the exact same values passed in the options 1`] = `
+Object {
+ "binary": true,
+ "date": Any,
+ "name": "FooBar",
+ "num": 1234,
+ "objectLiteral": Object {
+ "an": "object",
+ "literal": true,
+ "thiss": "is",
+ },
+}
+`;
+
+exports[`Fixture Factory - Integration Test Given a decorated class when using the related decorator with no/empty values then infer the value from the type itself 1`] = `
+Object {
+ "binary": Any,
+ "date": Any,
+ "name": Any,
+ "num": Any,
+}
+`;
diff --git a/test/integration/common/test-classes.ts b/test/integration/common/test-classes.ts
new file mode 100644
index 0000000..68b6b59
--- /dev/null
+++ b/test/integration/common/test-classes.ts
@@ -0,0 +1,71 @@
+import { Fixture } from '../../../src/decorators';
+
+enum TestEnum {
+ Foo = 'foo',
+ Bar = 111,
+ Bazz = 'Bazz1234',
+}
+
+export namespace TestClasses {
+ export class TestClassWithAbsoluteValues {
+ @Fixture('FooBar')
+ name: string;
+
+ @Fixture(1234)
+ num: number;
+
+ @Fixture(true)
+ binary: boolean;
+
+ @Fixture(new Date())
+ date: Date;
+
+ @Fixture({ thiss: 'is', an: 'object', literal: true })
+ objectLiteral: object;
+ }
+
+ export class TestClassWithCallback {
+ @Fixture((faker) => faker.internet.email())
+ email: string;
+
+ @Fixture((faker) => faker.name.firstName())
+ name: string;
+ }
+
+ export class TestClassWithNoValues {
+ @Fixture()
+ name: string;
+
+ @Fixture()
+ num: number;
+
+ @Fixture()
+ binary: boolean;
+
+ @Fixture()
+ date: Date;
+ }
+
+ export class TestClassWithEnum {
+ @Fixture({ enum: TestEnum })
+ someEnumVal: string;
+ }
+
+ class Dog {
+ @Fixture()
+ name: string;
+
+ @Fixture()
+ points: number;
+ }
+
+ export class TestClassWithSingleClass {
+ @Fixture(Dog)
+ dog: Dog;
+ }
+
+ export class TestClassWithMultiClass {
+ @Fixture({ type: Dog, count: 3 })
+ dogs: Dog[];
+ }
+}
diff --git a/test/integration/fixture-decorator.test.ts b/test/integration/fixture-decorator.test.ts
new file mode 100644
index 0000000..d79df56
--- /dev/null
+++ b/test/integration/fixture-decorator.test.ts
@@ -0,0 +1,103 @@
+import { TestClasses } from './common/test-classes';
+import { FixtureFactory } from '../../src/factories/fixture-factory';
+
+import TestClassWithAbsoluteValues = TestClasses.TestClassWithAbsoluteValues;
+import TestClassWithNoValues = TestClasses.TestClassWithNoValues;
+import TestClassWithCallback = TestClasses.TestClassWithCallback;
+import TestClassWithEnum = TestClasses.TestClassWithEnum;
+import TestClassWithOtherClass = TestClasses.TestClassWithSingleClass;
+import TestClassWithMultiClass = TestClasses.TestClassWithMultiClass;
+
+describe('Fixture Factory - Integration Test', () => {
+ let result;
+
+ describe('Given a decorated class', () => {
+ describe('when using the related decorator with absolute values', () => {
+ beforeAll(() => {
+ result = FixtureFactory.create(TestClassWithAbsoluteValues);
+ });
+
+ test('then return the exact same values passed in the options', () => {
+ expect(result).toMatchSnapshot({
+ date: expect.any(Date),
+ });
+ });
+ });
+
+ describe('when using the related decorator with a callback (faker)', () => {
+ beforeAll(() => {
+ result = FixtureFactory.create(TestClassWithCallback);
+ });
+
+ test('then return random values from faker', () => {
+ expect(result).toMatchObject({
+ email: expect.any(String),
+ name: expect.any(String),
+ });
+ });
+ });
+
+ describe('when using the related decorator with an enum value', () => {
+ beforeAll(() => {
+ result = FixtureFactory.create(TestClassWithEnum);
+ });
+
+ test('then return one random value (not key)', () => {
+ expect(['foo', 111, 'Bazz1234']).toContain(result.someEnumVal);
+ });
+ });
+
+ describe('when using the related decorator with no/empty values', () => {
+ beforeAll(() => {
+ result = FixtureFactory.create(TestClassWithNoValues);
+ });
+
+ test('then infer the value from the type itself', () => {
+ expect(result).toMatchSnapshot({
+ name: expect.any(String),
+ num: expect.any(Number),
+ binary: expect.any(Boolean),
+ date: expect.any(Date),
+ });
+ });
+ });
+
+ describe('when using the related decorator with a single class', () => {
+ beforeAll(() => {
+ result = FixtureFactory.create(TestClassWithOtherClass);
+ });
+
+ test('then return an object with the given class', () => {
+ expect(Object.keys(result.dog)).toEqual(['name', 'points']);
+ });
+ });
+
+ describe('when using the related decorator with a multi class', () => {
+ beforeAll(() => {
+ result = FixtureFactory.create(TestClassWithMultiClass);
+ });
+
+ test("then return contain a property 'dogs' which is array of Dog with length of 'count'", () => {
+ expect(result.dogs).toBeInstanceOf(Array);
+ expect(result.dogs).toHaveLength(3);
+ });
+
+ test('then return array of objects with the given class keys', () => {
+ expect(result.dogs).toEqual(
+ expect.arrayContaining([expect.objectContaining({ name: expect.any(String), points: expect.any(Number) })])
+ );
+ });
+ });
+
+ describe("when using the related decorator with 'count' option", () => {
+ beforeAll(() => {
+ result = FixtureFactory.create(TestClassWithAbsoluteValues, { count: 4, locale: 'ja' });
+ });
+
+ test("then return array with length of 'count'", () => {
+ expect(result).toBeInstanceOf(Array);
+ expect(result).toHaveLength(4);
+ });
+ });
+ });
+});
diff --git a/tsconfig.build.json b/tsconfig.build.json
new file mode 100644
index 0000000..224d083
--- /dev/null
+++ b/tsconfig.build.json
@@ -0,0 +1,20 @@
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "incremental": false,
+ "baseUrl": "./",
+ "rootDir": "./src",
+ "outDir": "./dist"
+ },
+ "exclude": [
+ "sample",
+ "node_modules",
+ "test",
+ "dist",
+ "**/*test.ts",
+ "index.ts",
+ "index.js",
+ "index.d.ts",
+ "commitlint.config.js"
+ ]
+}
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..8b29a44
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,36 @@
+{
+ "compilerOptions": {
+ "module": "commonjs",
+ "declaration": true,
+ "removeComments": false,
+ "emitDecoratorMetadata": true,
+ "experimentalDecorators": true,
+ "target": "es5",
+ "sourceMap": true,
+ "baseUrl": "./",
+ "outDir": "./dist",
+ "incremental": false,
+ "noImplicitAny": false,
+ "strictNullChecks": false,
+ "moduleResolution": "node",
+ "esModuleInterop": true,
+ "resolveJsonModule": true,
+ "types": [
+ "node",
+ "jest"
+ ],
+ "allowJs": true,
+ "lib": [
+ "es6"
+ ],
+ "skipLibCheck": true,
+ "allowSyntheticDefaultImports": true
+ },
+ "exclude": [
+ "index.ts",
+ "node_modules",
+ "dist",
+ "test",
+ "coverage"
+ ]
+}