-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(fixtures): add basic fixtures (and snapshots) functionality (#105)
* feat(fixtures): add basic fixtures module, skeleton and basic logic * refactor(fixtures): improve logic, change types/interfaces, add ioc typedi * refactor(fixtures): redesign classes and rearrange files, improve error texts * style(fixtures): functions name changes + adding deps
- Loading branch information
Showing
18 changed files
with
378 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
dist | ||
jest.config.js |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"extends": "../../.eslintrc" | ||
} |
Empty file.
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './src'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
const base = require('../../jest.config.base'); | ||
const packageJson = require('./package'); | ||
|
||
module.exports = { | ||
...base, | ||
name: packageJson.name, | ||
displayName: packageJson.name, | ||
collectCoverageFrom: ['src/**/*.ts', 'test/**/*.test.js'] | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
{ | ||
"name": "@mockinbird/fixtures", | ||
"version": "0.0.0", | ||
"license": "MIT", | ||
"main": "dist/index.js", | ||
"types": "dist/index.d.ts", | ||
"description": "Mockingbird Fixtures Generator Package", | ||
"contributors": [ | ||
{ | ||
"name": "Omer Morad", | ||
"email": "omer.moradd@gmail.com" | ||
}, | ||
{ | ||
"name": "Idan Ptichi", | ||
"email": "idanpt@gmail.com" | ||
} | ||
], | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/omermorad/mockingbird.git" | ||
}, | ||
"bugs": { | ||
"url": "https://github.com/omermorad/mockingbird/issues" | ||
}, | ||
"readme": "https://github.com/omermorad/mockingbird/tree/refactor/master/packages/fixtures/README.md", | ||
"scripts": { | ||
"prebuild": "yarn rimraf dist", | ||
"build": "tsc", | ||
"watch": "tsc --watch", | ||
"test": "jest --runInBand --verbose", | ||
"lint": "eslint '{src,test}/**/*.ts'", | ||
"lint:fix": "eslint '{src,test}/**/*.ts' --fix" | ||
}, | ||
"files": [ | ||
"dist", | ||
"index.js", | ||
"index.d.ts", | ||
"README.md", | ||
"CHANGELOG.md" | ||
], | ||
"dependencies": { | ||
"@mockinbird/logger": "^0.0.0", | ||
"@mockinbird/reflect": "^3.0.1", | ||
"@plumier/reflect": "^1.0.5", | ||
"lodash.get": "^4.4.2", | ||
"readdir": "^1.0.2", | ||
"reflect-metadata": "^0.1.13" | ||
}, | ||
"devDependencies": { | ||
"@mockinbird/common": "^2.0.2", | ||
"@types/lodash.get": "^4.4.6", | ||
"jest": "27.0.6", | ||
"rimraf": "^3.0.2", | ||
"ts-jest": "^27.0.3", | ||
"ts-loader": "^6.2.2", | ||
"ts-node": "8.10.2", | ||
"tsconfig-paths": "^3.9.0", | ||
"typedi": "^0.10.0", | ||
"typescript": "^3.9.7" | ||
}, | ||
"publishConfig": { | ||
"access": "public" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import { decorateClass } from '@plumier/reflect'; | ||
import { Class } from '@mockinbird/common'; | ||
|
||
export const FIXTURE_DECORATOR_NAME = 'Fixture'; | ||
|
||
interface FixtureDecoratorOptions { | ||
class: Class; | ||
} | ||
|
||
/** | ||
* | ||
* @param name {string} | ||
*/ | ||
export function Fixture(name: string): ClassDecorator; | ||
|
||
/** | ||
* | ||
* @param name {string} | ||
* @param origin { class: Class } | ||
*/ | ||
export function Fixture(name: string, origin: { class: Class }): ClassDecorator; | ||
|
||
export function Fixture(name: string, options?: FixtureDecoratorOptions): ClassDecorator { | ||
return decorateClass({ | ||
type: FIXTURE_DECORATOR_NAME, | ||
value: { name, options }, | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export * from './decorators/fixture.decorator'; | ||
export * from './lib/fixture-loader'; | ||
export * from './lib/fixture-scanner'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import { ClassLiteral } from '@mockinbird/common'; | ||
|
||
export type MockSnapshot<TClass> = { | ||
fixtureName: string; | ||
originFile: string; | ||
originClass: string; | ||
baseClass: string | undefined; | ||
values: Partial<ClassLiteral<TClass>>; | ||
variants?: { [key: string]: Omit<MockSnapshot<unknown>, 'originFile'> }; | ||
}; | ||
|
||
export interface SnapshotFile { | ||
readonly name: string; | ||
readonly path: string; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { ObjectLiteral } from '@mockinbird/common'; | ||
import get from 'lodash.get'; | ||
|
||
export class FixtureEngine { | ||
public static getFixturesDirectory(): string { | ||
const cwd = process.cwd(); | ||
// eslint-disable-next-line @typescript-eslint/no-var-requires | ||
const pkg = require(`${cwd}/package.json`) as ObjectLiteral; | ||
const { fixturesDir = '/fixtures' } = get(pkg, 'mockingbird'); | ||
|
||
return `${cwd}/${fixturesDir}`; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import * as fs from 'fs'; | ||
import { Container } from 'typedi'; | ||
import { FixtureEngine } from './fixture-engine'; | ||
import { SnapshotParser } from './snapshot-parser'; | ||
import { Snapshot } from './snapshot'; | ||
|
||
export interface FixtureLoader<TClass = any> { | ||
/** | ||
* | ||
* @param name {string} the name of the fixture variant | ||
*/ | ||
variant(name: string): Omit<FixtureLoader<TClass>, 'variant'>; | ||
|
||
/** | ||
* | ||
* @param fixtureName {string} the name of the fixture | ||
*/ | ||
load(fixtureName: string): Promise<TClass>; | ||
} | ||
|
||
export class FixtureLoader<TClass = any> { | ||
private fixtureVariantName: string; | ||
|
||
public constructor(private readonly fixtureName: string) {} | ||
|
||
public variant(name: string): Omit<this, 'variant'> { | ||
this.fixtureVariantName = name; | ||
return this; | ||
} | ||
|
||
public async load(): Promise<TClass> { | ||
const fixturesDir = FixtureEngine.getFixturesDirectory(); | ||
const snapshotPath = `${fixturesDir}/snapshots`; | ||
|
||
if (!fs.existsSync(snapshotPath)) { | ||
throw new Error(`Can not find directory 'snapshots' under directory '${fixturesDir}'`); | ||
} | ||
|
||
const snapshotParser = Container.get<SnapshotParser>(SnapshotParser); | ||
const snapshot = Snapshot.create<TClass>({ name: this.fixtureName, path: snapshotPath }); | ||
|
||
return snapshotParser.parse<TClass>(snapshot, this.fixtureVariantName); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import fs from 'fs'; | ||
import path from 'path'; | ||
import { Class } from '@mockinbird/common'; | ||
import { Logger } from '@mockinbird/logger'; | ||
import { ClassReflection, reflect } from '@plumier/reflect'; | ||
|
||
const AAAAA = '/Users/omermorad/projects/mockingbird/sample/entities-monorepo/mocks'; | ||
|
||
function isMockSnapshotFileExists(name: string) { | ||
return fs.existsSync(`${AAAAA}/snapshots/${name}.mock`); | ||
} | ||
|
||
export class FixtureScanner { | ||
public async scan() { | ||
const files = fs.readdirSync(AAAAA); | ||
const paths = []; | ||
|
||
const promises = files | ||
.filter((file) => path.extname(file) === '.ts') | ||
.map((file) => { | ||
const fullPath = `${AAAAA}/${file}`; | ||
paths.push(fullPath); | ||
|
||
return import(fullPath).catch((e) => { | ||
Logger.error(`Error importing file ${file}`, e); | ||
}); | ||
}); | ||
|
||
const reflections: { [key: string]: { file: string; reflection: ClassReflection } } = {}; | ||
const functions = await Promise.all<Class[]>(promises); | ||
|
||
functions.forEach((constructors, index) => { | ||
for (const [key, value] of Object.entries(constructors)) { | ||
reflections[key] = { file: paths[index], reflection: reflect(value) }; | ||
} | ||
}); | ||
|
||
for (const [key, value] of Object.entries<{ file: string; reflection: ClassReflection }>(reflections)) { | ||
const fixtureDecorators = value.reflection.decorators.find((decorator) => decorator.type === 'Fixture'); | ||
|
||
const b = 1; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
import get from 'lodash.get'; | ||
import { Service } from 'typedi'; | ||
import { reflect } from '@plumier/reflect'; | ||
import { Class } from '@mockinbird/common'; | ||
import { Logger } from '@mockinbird/logger'; | ||
import { FixtureEngine } from './fixture-engine'; | ||
import { Snapshot } from './snapshot'; | ||
import { FIXTURE_DECORATOR_NAME } from '../decorators/fixture.decorator'; | ||
|
||
@Service() | ||
export class SnapshotParser { | ||
public static isVariantExists(snapshot: Snapshot, variant: string): boolean { | ||
return snapshot.contents.variants.hasOwnProperty(variant); | ||
} | ||
|
||
public static async importOriginClass<TClass = unknown>( | ||
originFile: string, | ||
originClass: string | ||
): Promise<Class<TClass>> { | ||
const importedClasses: { [key: string]: Class<TClass> } = await import( | ||
`${FixtureEngine.getFixturesDirectory()}/${originFile}` | ||
); | ||
|
||
if (!importedClasses.hasOwnProperty(originClass)) { | ||
throw new Error( | ||
` | ||
Mockingbird was trying to import the class '${originClass}' from file '${originFile}' | ||
but only the class(es) '${Object.keys(importedClasses).join("', ")}' were found. | ||
The origin file does not contain any class named '${originClass}'. \n | ||
It might be that you have changed the name of the origin class or moved it to another | ||
file. | ||
Possible solution: hit "mockingbird regen" in you cli | ||
` | ||
); | ||
} | ||
|
||
return importedClasses[originClass]; | ||
} | ||
|
||
public static fetchDecoratorValues<TClass>(actualClass: Class<TClass>): { | ||
fixtureName: string; | ||
sourceClass: Class<TClass>; | ||
} { | ||
const { decorators = [] } = reflect(actualClass); | ||
const found = decorators.find((decorator) => decorator.type === FIXTURE_DECORATOR_NAME); | ||
|
||
return { | ||
fixtureName: get(found, 'value.name'), | ||
sourceClass: get(found, 'value.options.class') as Class<TClass>, | ||
}; | ||
} | ||
|
||
public async parse<TClass = any>(snapshot: Snapshot<TClass>, variant?: string): Promise<TClass> { | ||
const { originFile, originClass, fixtureName, variants = {}, values = {} } = snapshot.contents; | ||
|
||
const actualClass = await SnapshotParser.importOriginClass<TClass>(originFile, originClass); | ||
const { fixtureName: decoratorFixtureName, sourceClass } = SnapshotParser.fetchDecoratorValues(actualClass); | ||
|
||
let instance: TClass | any; | ||
|
||
if (decoratorFixtureName !== fixtureName) { | ||
throw new Error(`Mockingbird is able to find the file '${fixtureName}', but it does not contain any snapshot named '${fixtureName}'. | ||
'${decoratorFixtureName}' has been found associated to the same class '${originClass}' | ||
`); | ||
} | ||
|
||
if (sourceClass) { | ||
instance = new sourceClass(); | ||
} else { | ||
Logger.warn( | ||
`Fixture '${decoratorFixtureName}' does not contain any source class in the @Fixture decorator options,\nMockingbird will rely on the mock class '${originClass}' instead` | ||
); | ||
Logger.info(`If you want to instantiate it from a different class please add { src: <Class> } to your fixture`); | ||
|
||
instance = new actualClass(); | ||
} | ||
|
||
const fixture = Object.assign(instance, values); | ||
|
||
if (variant) { | ||
if (!SnapshotParser.isVariantExists(snapshot, variant)) { | ||
throw new Error(`Mockingbird can not find variant of fixture '${decoratorFixtureName}' named '${variant}'. | ||
Did you create a variant for your base fixture '${decoratorFixtureName}' named '${variant}'? | ||
Note: check the file '${FixtureEngine.getFixturesDirectory()}/${originFile}'`); | ||
} | ||
|
||
return Object.assign(fixture, variants[variant].values); | ||
} | ||
|
||
return fixture; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import * as fs from 'fs'; | ||
import { MockSnapshot, SnapshotFile } from '../interfaces/interfaces'; | ||
|
||
export class Snapshot<TClass = any> { | ||
private readonly snapshotContents: MockSnapshot<TClass>; | ||
|
||
public constructor(public readonly name: string, public readonly path: string) { | ||
this.snapshotContents = this.parse(); | ||
} | ||
|
||
private parse(): MockSnapshot<TClass> { | ||
const { name, path } = this; | ||
|
||
try { | ||
if (!fs.existsSync(`${path}/${name}.fixture.json`)) { | ||
throw new Error(`${path}/${name}.fixture.json not found`); | ||
} | ||
|
||
const snapshotContents = fs.readFileSync(`${path}/${name}.fixture.json`, 'utf-8'); | ||
return JSON.parse(snapshotContents) as MockSnapshot<TClass>; | ||
} catch (error) { | ||
throw new Error( | ||
` | ||
Mockingbird can not find fixture named '${name}'. \n | ||
Maybe you are trying to load a variant of another fixture? | ||
Possible solution: MockFactory(<base-fixture-name>).variant(<fixture-variant-name>) | ||
Looked for file '${name}.fixture.json' under ${path} | ||
` | ||
); | ||
} | ||
} | ||
|
||
public static create<TClass = any>(snapshot: SnapshotFile): Snapshot<TClass> { | ||
return new Snapshot<TClass>(snapshot.name, snapshot.path); | ||
} | ||
|
||
public get contents(): MockSnapshot<TClass> { | ||
return this.snapshotContents; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
{ | ||
"extends": "../tsconfig.build.json", | ||
"compilerOptions": { | ||
"rootDir": "./src", | ||
"outDir": "./dist", | ||
"target": "es6" | ||
}, | ||
"exclude": [ | ||
"node_modules", | ||
"test", | ||
"src/**/*.test.ts", | ||
"index.ts" | ||
], | ||
"include": ["src/"] | ||
} |
Oops, something went wrong.