Skip to content

Commit

Permalink
feat(fluent-api): add fluent api (builder) functionallity and persist…
Browse files Browse the repository at this point in the history
…ance

Add fluent API to enable methods chanining with ability to persist the mock

BREAKING CHANGE: MockFactory is now a function and not a class, changed the original to
MockGenerator. Add fluent API and ability to persist mock datta

closes #42, closes #37
  • Loading branch information
omermorad committed Jul 13, 2021
1 parent 4d46105 commit a7d92bb
Show file tree
Hide file tree
Showing 6 changed files with 151 additions and 10 deletions.
14 changes: 14 additions & 0 deletions sample/mockingbird-typeorm/test/__mocks__/app-e2e.test.mock.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"Dog": {
"this-is-a-slug": [
{
"name": "Rexy",
"point": 10,
},
{
"name": "Rexy",
"point": 10
}
]
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { MockFactory } from './mock-factory';
import { MockGenerator } from './mock-generator';

const processMock = jest.fn();

Expand All @@ -8,7 +8,7 @@ jest.mock('../class-processor', () => ({
}),
}));

describe('MockFactory - Unit', () => {
describe('MockGenerator - Unit', () => {
describe('given a Mock Factory', () => {
afterEach(() => {
processMock.mockClear();
Expand All @@ -18,7 +18,7 @@ describe('MockFactory - Unit', () => {

describe("when calling 'create' method without options", () => {
test('then call process exactly once', () => {
MockFactory.create(TestClass);
MockGenerator.create(TestClass);

expect(processMock).toHaveBeenCalledTimes(1);
expect(processMock).toHaveBeenCalledWith(TestClass);
Expand All @@ -29,7 +29,7 @@ describe('MockFactory - Unit', () => {
const count = 3;

test('then call process 3 times ', () => {
MockFactory.create(TestClass, { count });
MockGenerator.create(TestClass, { count });

expect(processMock).toHaveBeenCalledTimes(count);
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import faker from 'faker';
import { ClassProcessor } from '../class-processor';
import { ClassProcessor } from '../lib/class-processor';
import { Class } from '../types/mock-options.type';
import { MockDecoratorFactoryOptions } from '../types';
import { ClassReflector } from '../class-reflector';
import { ClassReflector } from '../lib/class-reflector';

export class MockFactory {
export class MockGenerator {
/**
* Return an object with all the properties decorated by the 'Mock' Decorator
*
Expand All @@ -14,7 +14,7 @@ export class MockFactory {
*
* @param target
*/
public static create<TClass extends any = any>(target: Class<TClass>): TClass;
public static create<TClass extends Class = any>(target: Class<TClass>): TClass;

/**
* Return an array of objects with all the properties decorated by the
Expand All @@ -32,7 +32,7 @@ export class MockFactory {
* @param target
* @param options
*/
public static create<TClass extends any = any>(target: Class<TClass>, options: MockDecoratorFactoryOptions): TClass[];
public static create<TClass = any>(target: Class<TClass>, options: MockDecoratorFactoryOptions): TClass[];

/**
* Return one or many objects (array) with all the properties decorated
Expand All @@ -41,7 +41,7 @@ export class MockFactory {
* @param targetClass
* @param options
*/
public static create<TClass extends any = any>(
public static create<TClass = any>(
targetClass: Class<TClass>,
options?: MockDecoratorFactoryOptions
): TClass | TClass[] {
Expand Down
63 changes: 63 additions & 0 deletions src/lib/mock-builder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { MockGenerator } from '../factories';
import { Class, ClassLiteral, GeneratedMock } from '../types';
import { MockPersistent } from './mock-persistent';

export class MockBuilder<TClass extends Class> {
private doPersist = false;
private slug = undefined;
private isPlain = false;
private locale = 'en';

public constructor(private readonly targetClass: Class, private readonly persistent: MockPersistent) {}

private process(mock: TClass[]): TClass[] | ClassLiteral<TClass>[];
private process(mock: TClass): TClass | ClassLiteral<TClass>;

private process(mock: TClass | TClass[]): GeneratedMock | TClass[] | ClassLiteral<TClass>[] {
let newMock;

if (this.isPlain) {
if (mock.length > 1) {
newMock = (mock as TClass[]).map((mock) => Object.assign({}, mock));
}

newMock = Object.assign({}, mock);
}

if (this.doPersist) {
return this.persistent.process({
slug: this.slug,
baseMock: newMock,
});
}

return newMock;
}

public setLocale(locale: string): this {
this.locale = locale;
return this;
}

public toPlain(): this {
this.isPlain = true;
return this;
}

public many(count: number): TClass[] {
const mock: TClass[] = MockGenerator.create<TClass>(this.targetClass, { locale: this.locale, count });
return this.process(mock);
}

public one(): TClass {
const mock: TClass = MockGenerator.create<TClass>(this.targetClass); // TODO: Add locale
return this.process(mock);
}

public persist(slug: string): this {
this.doPersist = true;
this.slug = slug;

return this;
}
}
16 changes: 16 additions & 0 deletions src/lib/mock-factory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import fs from 'fs';
import { Class } from '../types';
import { MockBuilder } from './mock-builder';
import { MockPersistent } from './mock-persistent';

export function MockFactory<TClass extends Class = any>(target: Class): MockBuilder<TClass> {
const persistent = new MockPersistent(fs, {
fileName: '',
});

return new MockBuilder<TClass>(target, persistent);
}

class Dog {}

MockFactory(Dog).toPlain().persist('lots-of-dogs').many(3);
48 changes: 48 additions & 0 deletions src/lib/mock-persistent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import * as fileSystem from 'fs';
import { GeneratedMock } from '../types';

export interface PersistentDto {
baseMock: GeneratedMock;
slug: string;
}

export class MockPersistent {
private mockData: GeneratedMock = {};

public constructor(private readonly fs: typeof fileSystem, private readonly options: { fileName: string }) {
this.readFile();
}

private readFile() {
const { fileName } = this.options;

if (this.fs.existsSync(fileName)) {
try {
const fileData = this.fs.readFileSync(fileName, 'utf-8');
this.mockData = JSON.parse(fileData);
} catch (e) {
console.error(`The mock file '${fileName}' can not be parsed due to JSON error`);
}
}
}

private saveFile() {
const data = JSON.stringify(this.mockData);
this.fs.writeFileSync(this.options.fileName, data);
}

public process(config: PersistentDto): GeneratedMock {
const { baseMock, slug } = config;

if (this.mockData.hasOwnProperty(slug)) {
return this.mockData[slug];
}

console.log(`The slug '${slug}' can not be found inside '${this.options.fileName}', creating one..`);

this.mockData[slug] = baseMock;
this.saveFile();

return baseMock;
}
}

0 comments on commit a7d92bb

Please sign in to comment.