From cf56561caa4d56bbcc489128e49743c0d806299b Mon Sep 17 00:00:00 2001 From: Omer Morad Date: Sun, 18 Jul 2021 17:02:30 +0300 Subject: [PATCH] refactor(mockingbird-ts): arrange classes - change mock-factory to mock-generator (#55) * refactor(mockingbird-ts): change the name of mock factory to be mock generator change MockFactory name to be MockGenerator BREAKING CHANGE: MockFactory changed to be MockGenerator re #42 * chore(repo): update tsconfig build config * refactor(types): change 'class' to 'type' with new definition * refactor(reflect): change 'class' type to 'type' type * refactor(parser): change 'class' type to 'type' * refactor(mockingbird-ts): generator now has state and di, remove static method 'create' * test(mockingbird-ts): add unit test for mock generator * test(mockingbird-ts): add unit test for mock generator * refactor(parser): add option to set locale and change static class member (state instead) * test(mockingbird-ts): remove test of circular mock --- .../generator/src/lib/mock-generator.test.ts | 69 +++++++++++-------- packages/generator/src/lib/mock-generator.ts | 38 +++++----- .../test/integration/mock-generator.test.ts | 21 ++++-- .../generator/test/mock-factory-circular.ts | 30 -------- .../src/handlers/abstract-value-handler.ts | 4 +- .../src/handlers/array-value-handler.test.ts | 4 +- .../handlers/single-class-value-handler.ts | 4 +- packages/parser/src/lib/class-parser.ts | 25 ++++--- packages/reflect/src/lib/class-reflector.ts | 6 +- .../reflect/src/types/mock-options.type.ts | 6 +- packages/tsconfig.build.json | 11 ++- packages/types/index.ts | 16 ++--- 12 files changed, 116 insertions(+), 118 deletions(-) delete mode 100644 packages/generator/test/mock-factory-circular.ts diff --git a/packages/generator/src/lib/mock-generator.test.ts b/packages/generator/src/lib/mock-generator.test.ts index 1020b98..dd402bf 100644 --- a/packages/generator/src/lib/mock-generator.test.ts +++ b/packages/generator/src/lib/mock-generator.test.ts @@ -1,37 +1,52 @@ +import { ClassParser } from '@mockinbird/parser'; import { MockGenerator } from './mock-generator'; -const processMock = jest.fn(); - -jest.mock('@mockinbird/parser', () => ({ - ClassParser: jest.fn().mockImplementation(() => { - return { parse: processMock }; - }), -})); - -describe('MockGenerator - Unit', () => { - describe('given a Mock Factory', () => { - afterEach(() => { - processMock.mockClear(); +/** + * The full test of MockGenerator can be found under the 'test' folder, + * you can find there the full integration test + */ +describe('MockGenerator', () => { + describe('given a MockGenerator', () => { + let generator: MockGenerator; + + const parserMock = { + setFakerLocale: jest.fn(), + parse: jest.fn(), + } as unknown as ClassParser; + + beforeAll(() => { + generator = new MockGenerator(parserMock); }); - class TestClass {} - - describe("when calling 'create' method without options", () => { - test('then call process exactly once', () => { - MockGenerator.create(TestClass); - - expect(processMock).toHaveBeenCalledTimes(1); - expect(processMock).toHaveBeenCalledWith(TestClass); + describe("when calling 'create' method", () => { + describe('with no options at all', () => { + test('then setup parser with the default locale', () => { + generator.create(class TestClass {}); + expect(parserMock.setFakerLocale).toHaveBeenCalledWith('en'); + }); }); - }); - - describe("when calling 'create' method with count = 3", () => { - const count = 3; - test('then call process 3 times ', () => { - MockGenerator.create(TestClass, { count }); + describe('with a given locale (as argument)', () => { + test('then setup the parser with the given locale', () => { + generator.create(class TestClass {}, 'arbitrary-locale'); + expect(parserMock.setFakerLocale).toHaveBeenCalledWith('arbitrary-locale'); + }); + }); - expect(processMock).toHaveBeenCalledTimes(count); + describe('with an object including options', () => { + describe("and the options including only 'count'", function () { + test("then call the parser 'count' times", () => { + generator.create(class TestClass {}, { count: 3 }); + expect(parserMock.setFakerLocale).toHaveBeenCalledTimes(3); + }); + }); + + describe("and the options including both 'count' and 'locale", function () { + test('then setup the parser with the locale from the options', () => { + generator.create(class TestClass {}, { count: 1, locale: 'arbitrary-locale' }); + expect(parserMock.setFakerLocale).toHaveBeenCalledWith('arbitrary-locale'); + }); + }); }); }); }); diff --git a/packages/generator/src/lib/mock-generator.ts b/packages/generator/src/lib/mock-generator.ts index 2a7cc57..e5f4ac7 100644 --- a/packages/generator/src/lib/mock-generator.ts +++ b/packages/generator/src/lib/mock-generator.ts @@ -1,11 +1,12 @@ -import { Class, Faker } from '@mockinbird/types'; +import { Type } from '@mockinbird/types'; import { ClassParser } from '@mockinbird/parser'; -import { ClassReflector } from '@mockinbird/reflect'; import { MockDecoratorFactoryOptions } from '../types/mock-decorator-factory-options.interface'; -export class MockGenerator { +export class MockGenerator { private static readonly DEFAULT_LOCALE = 'en'; + public constructor(private readonly classParser: ClassParser) {} + /** * Return an object with all the properties decorated by the 'Mock' Decorator * @@ -13,9 +14,10 @@ export class MockGenerator { * class Person { @Mock() name: string } * MockGenerator.create(Person) will return an object { name: } * - * @param target + * @param targetClass + * @param locale */ - public static create(target: Class): TClass; + public create(targetClass: Type, locale?: string): TClass; /** * Return an array of objects with all the properties decorated by the @@ -30,10 +32,10 @@ export class MockGenerator { * Passing a 'locale' property will set a different locale for faker calls * The default locale is 'en' (english) * - * @param target + * @param targetClass * @param options */ - public static create(target: Class, options: MockDecoratorFactoryOptions): TClass[]; + public create(targetClass: Type, options: MockDecoratorFactoryOptions): TClass[]; /** * Return one or many objects (array) with all the properties decorated @@ -42,24 +44,28 @@ export class MockGenerator { * @param targetClass * @param options */ - public static create( - targetClass: Class, - options?: MockDecoratorFactoryOptions - ): TClass | TClass[] { - const { count = 1, locale = this.DEFAULT_LOCALE } = options || {}; + public create(targetClass: Type, options?: MockDecoratorFactoryOptions | string): TClass | TClass[] { + let locale: string; + + if (typeof options === 'string') { + locale = options; + } else { + locale = options?.locale || MockGenerator.DEFAULT_LOCALE; + } - Faker.setLocale(locale); + this.classParser.setFakerLocale(locale); - const parser = new ClassParser(Faker, new ClassReflector()); + const { count = 1 } = (options || {}) as MockDecoratorFactoryOptions; if (!count || count === 1) { - return parser.parse(targetClass); + return this.classParser.parse(targetClass); } const classInstances: TClass[] = []; for (let i = 1; i <= count; i++) { - classInstances.push(parser.parse(targetClass)); + const parsedClass = this.classParser.parse(targetClass); + classInstances.push(parsedClass); } return classInstances; diff --git a/packages/generator/test/integration/mock-generator.test.ts b/packages/generator/test/integration/mock-generator.test.ts index 8258f28..e27a6fd 100644 --- a/packages/generator/test/integration/mock-generator.test.ts +++ b/packages/generator/test/integration/mock-generator.test.ts @@ -1,3 +1,6 @@ +import { ClassParser } from '@mockinbird/parser'; +import { ClassReflector } from '@mockinbird/reflect'; +import { Faker } from '@mockinbird/types'; import { TestClasses } from './common/test-classes'; import { MockGenerator } from '../../src'; @@ -11,10 +14,14 @@ import TestClassWithMultiClass = TestClasses.TestClassWithMultiClass; describe('MockGenerator - Integration Test', () => { let result; + const reflector = new ClassReflector(); + const parser = new ClassParser(Faker, reflector); + const generator = new MockGenerator(parser); + describe('given a decorated class', () => { describe('when using the @Mock decorator with absolute values', () => { beforeAll(() => { - result = MockGenerator.create(TestClassWithAbsoluteValues); + result = generator.create(TestClassWithAbsoluteValues); }); test('then return the exact same values passed in the options', () => { @@ -26,7 +33,7 @@ describe('MockGenerator - Integration Test', () => { describe('when using the @Mock decorator with a callback (faker)', () => { beforeAll(() => { - result = MockGenerator.create(TestClassWithCallback); + result = generator.create(TestClassWithCallback); }); test('then return random values from faker', () => { @@ -39,7 +46,7 @@ describe('MockGenerator - Integration Test', () => { describe('when using the @Mock decorator with an enum decoratorValue', () => { beforeAll(() => { - result = MockGenerator.create(TestClassWithEnum); + result = generator.create(TestClassWithEnum); }); test('then return one random decoratorValue (not key)', () => { @@ -49,7 +56,7 @@ describe('MockGenerator - Integration Test', () => { describe('when using the @Mock decorator with no/empty values', () => { beforeAll(() => { - result = MockGenerator.create(TestClassWithNoValues); + result = generator.create(TestClassWithNoValues); }); test('then infer the decoratorValue from the type itself', () => { @@ -64,7 +71,7 @@ describe('MockGenerator - Integration Test', () => { describe('when using the @Mock decorator with a single class', () => { beforeAll(() => { - result = MockGenerator.create(TestClassWithOtherClass); + result = generator.create(TestClassWithOtherClass); }); test('then return an object with the given class', () => { @@ -74,7 +81,7 @@ describe('MockGenerator - Integration Test', () => { describe('when using the @Mock decorator with a multi class', () => { beforeAll(() => { - result = MockGenerator.create(TestClassWithMultiClass); + result = generator.create(TestClassWithMultiClass); }); test("then return contain a property 'dogs' which is array of Dog with length of 'count'", () => { @@ -91,7 +98,7 @@ describe('MockGenerator - Integration Test', () => { describe("when using the @Mock decorator with 'count' option", () => { beforeAll(() => { - result = MockGenerator.create(TestClassWithAbsoluteValues, { count: 4, locale: 'ja' }); + result = generator.create(TestClassWithAbsoluteValues, { count: 4, locale: 'ja' }); }); test("then return array with length of 'count'", () => { diff --git a/packages/generator/test/mock-factory-circular.ts b/packages/generator/test/mock-factory-circular.ts deleted file mode 100644 index 387d062..0000000 --- a/packages/generator/test/mock-factory-circular.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { Mock } from '@mockinbird/reflect'; -import { MockGenerator } from '../src'; - -describe('Mock Factory - circular class-type', () => { - describe('with single class circular mock', () => { - class Man { - @Mock(Man) - readonly son: Man; - } - - test('when calling MockGenerator.create it throws an exception', () => { - expect(() => MockGenerator.create(Man)).toThrowError( - 'Circular class-type mock detected! Target: Man; PropertyInterface: son' - ); - }); - }); - - describe('with multiple class circular mock', () => { - class AnotherMan { - @Mock({ type: AnotherMan, count: 3 }) - readonly sons: AnotherMan[]; - } - - test('When calling MockGenerator.create it throws an exception', () => { - expect(() => MockGenerator.create(AnotherMan)).toThrowError( - 'Circular class-type mock detected! Target: AnotherMan; PropertyInterface: sons' - ); - }); - }); -}); diff --git a/packages/parser/src/handlers/abstract-value-handler.ts b/packages/parser/src/handlers/abstract-value-handler.ts index 62ef1ee..f5fb360 100644 --- a/packages/parser/src/handlers/abstract-value-handler.ts +++ b/packages/parser/src/handlers/abstract-value-handler.ts @@ -1,6 +1,6 @@ -import { Class, Faker } from '@mockinbird/types'; +import { Type, Faker } from '@mockinbird/types'; import { ClassParser } from '../lib/class-parser'; export class AbstractValueHandler { - public constructor(protected readonly faker?: Faker, protected readonly classParser?: ClassParser) {} + public constructor(protected readonly faker?: Faker, protected readonly classParser?: ClassParser) {} } diff --git a/packages/parser/src/handlers/array-value-handler.test.ts b/packages/parser/src/handlers/array-value-handler.test.ts index 6be0a5d..ff1cbc4 100644 --- a/packages/parser/src/handlers/array-value-handler.test.ts +++ b/packages/parser/src/handlers/array-value-handler.test.ts @@ -1,5 +1,5 @@ import { Property, PropertyDecoratorValue } from '@mockinbird/reflect'; -import { Class, Faker, MultiClass } from '@mockinbird/types'; +import { Type, Faker, MultiClass } from '@mockinbird/types'; import { ArrayValueHandler } from './array-value-handler'; import { ClassParser } from '../lib/class-parser'; @@ -68,7 +68,7 @@ describe('ArrayValueHandler Unit Test', () => { }); test('then return an array of String(s) only', () => { - const constructorIsString = (item) => (item as Class).constructor.name === 'String'; + const constructorIsString = (item) => (item as Type).constructor.name === 'String'; expect(result.every(constructorIsString)).toBeTruthy(); }); }); diff --git a/packages/parser/src/handlers/single-class-value-handler.ts b/packages/parser/src/handlers/single-class-value-handler.ts index 6637be4..bcd2fbf 100644 --- a/packages/parser/src/handlers/single-class-value-handler.ts +++ b/packages/parser/src/handlers/single-class-value-handler.ts @@ -1,5 +1,5 @@ import { Property } from '@mockinbird/reflect'; -import { Class } from '@mockinbird/types'; +import { Type } from '@mockinbird/types'; import { AbstractValueHandler } from './abstract-value-handler'; import { ValueHandler } from '../types/value-handler.interface'; import { isPrimitive } from '../common/is-primitive'; @@ -10,6 +10,6 @@ export class SingleClassValueHandler extends AbstractValueHandler implements Val } public produceValue(propertyDto: Property): any { - return this.classParser.parse(propertyDto.decoratorValue.value as Class); + return this.classParser.parse(propertyDto.decoratorValue.value as Type); } } diff --git a/packages/parser/src/lib/class-parser.ts b/packages/parser/src/lib/class-parser.ts index 0990ac0..10146d0 100644 --- a/packages/parser/src/lib/class-parser.ts +++ b/packages/parser/src/lib/class-parser.ts @@ -1,5 +1,5 @@ import { Property, ClassReflector } from '@mockinbird/reflect'; -import { Class, Faker } from '@mockinbird/types'; +import { Type, Faker } from '@mockinbird/types'; import { CallbackValueHandler } from '../handlers/callback-value-handler'; import { ObjectLiteralValueHandler } from '../handlers/object-literal-value-handler'; import { EnumValueHandler } from '../handlers/enum-value-handler'; @@ -8,12 +8,13 @@ import { SingleClassValueHandler } from '../handlers/single-class-value-handler' import { PrimitiveValueHandler } from '../handlers/primitive-value-handler'; import { ValueHandler } from '../types/value-handler.interface'; -export interface ClassParser { - parse(target: Class): T; +export interface ClassParser { + parse(target: Type): TClass; + setFakerLocale(locale: Faker['locale']): void; } -export class ClassParser { - private static readonly VALUE_HANDLERS: Class[] = [ +export class ClassParser { + private readonly valueHandlers: Type[] = [ EnumValueHandler, ArrayValueHandler, SingleClassValueHandler, @@ -24,29 +25,33 @@ export class ClassParser { public constructor(private readonly faker: Faker, private readonly reflector: ClassReflector) {} - private handlePropertyValue(property: Property): T | T[] { - for (const classHandler of ClassParser.VALUE_HANDLERS) { + private handlePropertyValue(property: Property): TClass | TClass[] { + for (const classHandler of this.valueHandlers) { const handler = new classHandler(this.faker, this); if (handler.shouldHandle(property)) { - return handler.produceValue(property); + return handler.produceValue(property); } } } + public setFakerLocale(locale: Faker['locale']): void { + this.faker.setLocale(locale); + } + /** * Return an object from the target class with all the properties * decorated by the 'Mock' Decorator * * @param targetClass */ - public parse(targetClass: Class): T { + public parse(targetClass: Type): TClass { if (!targetClass) { throw new Error(`Target class is 'undefined'`); } const classReflection = this.reflector.reflectClass(targetClass); - const classInstance: T = new targetClass(); + const classInstance: TClass = new targetClass(); const props = classReflection.reduce((acc, property) => { return { ...acc, [property.name]: this.handlePropertyValue(property) }; diff --git a/packages/reflect/src/lib/class-reflector.ts b/packages/reflect/src/lib/class-reflector.ts index 94abfdb..41fee51 100644 --- a/packages/reflect/src/lib/class-reflector.ts +++ b/packages/reflect/src/lib/class-reflector.ts @@ -1,6 +1,6 @@ -import { Class } from '@mockinbird/types'; +import { Type } from '@mockinbird/types'; import reflect, { ClassReflection, PropertyReflection } from '@plumier/reflect'; -import { MockOptions } from '../types/mock-options.type'; +import { MockOptions } from '../types'; import { MOCK_DECORATOR_NAME } from '../decorators/mock.decorator'; import { Property } from './property'; import { ClassReflectionDto } from '../types/class-reflection-dto.type'; @@ -23,7 +23,7 @@ export class ClassReflector { }); } - public reflectClass(target: Class): ClassReflectionDto { + public reflectClass(target: Type): ClassReflectionDto { if (!ClassReflector.REFLECTED_CLASSES.hasOwnProperty(target.name)) { ClassReflector.REFLECTED_CLASSES[target.name] = this.extractDecoratedProperties(reflect(target)); } diff --git a/packages/reflect/src/types/mock-options.type.ts b/packages/reflect/src/types/mock-options.type.ts index 72a683d..4367974 100644 --- a/packages/reflect/src/types/mock-options.type.ts +++ b/packages/reflect/src/types/mock-options.type.ts @@ -1,5 +1,5 @@ -import { Callback, Class, ClassLiteral, EnumObject, ExactValue, MultiClass } from '@mockinbird/types'; +import { Callback, Type, ClassLiteral, EnumObject, ExactValue, MultiClass } from '@mockinbird/types'; -export type MockOptions = Callback | ExactValue | Class | EnumObject | MultiClass; +export type MockOptions = Callback | ExactValue | Type | EnumObject | MultiClass; -export type GeneratedMock = Class | ClassLiteral; +export type GeneratedMock = Type | ClassLiteral; diff --git a/packages/tsconfig.build.json b/packages/tsconfig.build.json index 58e4e11..ea71bfb 100644 --- a/packages/tsconfig.build.json +++ b/packages/tsconfig.build.json @@ -1,19 +1,16 @@ { "extends": "../tsconfig.json", "compilerOptions": { - "incremental": false + "incremental": false, + "sourceMap": true }, "exclude": [ - "../sample", "node_modules", - "packages/mockingbird-ts/test", "dist", - "**/*test.ts", + "*.test.ts", "index.ts", "index.js", "index.d.ts", - "coverage", - "jest.config.js", - "commitlint.config.js" + "coverage" ] } diff --git a/packages/types/index.ts b/packages/types/index.ts index 897f937..58b02e9 100644 --- a/packages/types/index.ts +++ b/packages/types/index.ts @@ -8,22 +8,20 @@ export interface ObjectLiteral { [key: string]: any; } +export interface Type extends Function { + new (...args: any[]): T; +} + export type ExactValue = string | number | boolean | ObjectLiteral | Date; -export type MultiClass = { type: Class; count: number }; +export type MultiClass = { type: Type; count: number }; export type EnumObject = { enum: Record }; export type Callback = (faker: FakerStatic) => any; -// export interface Class extends Function { -// new (...args: any[]): T; -// } - -export type Class = new (...arg: any[]) => T; - export type ClassLiteral = { [K in keyof TClass]: TClass[K] }; -export type MockOptions = Callback | ExactValue | Class | EnumObject | MultiClass; +export type MockOptions = Callback | ExactValue | Type | EnumObject | MultiClass; -export type GeneratedMock = Class | ClassLiteral; +export type GeneratedMock = Type | ClassLiteral;