├── packages ├── .gitkeep ├── playground │ ├── src │ │ ├── assets │ │ │ └── README.md │ │ ├── index.ts │ │ ├── user.ts │ │ ├── product │ │ │ ├── product.ts │ │ │ ├── product.module.ts │ │ │ ├── product.controller.ts │ │ │ └── product.controller.spec.ts │ │ ├── customer │ │ │ ├── customer.ts │ │ │ ├── customer.module.ts │ │ │ ├── customer.controller.ts │ │ │ └── customer.controller.spec.ts │ │ ├── main.ts │ │ ├── customer-product │ │ │ └── customer-product.module.ts │ │ ├── app.module.ts │ │ └── app.controller.ts │ ├── .eslintrc.json │ ├── tsconfig.spec.json │ ├── tsconfig.json │ ├── jest.config.js │ └── tsconfig.app.json ├── docs │ ├── docs │ │ ├── docs.md │ │ ├── IMDB-description.md │ │ ├── IMDB-installation.md │ │ ├── IMDB-entitycontroller.md │ │ ├── IMDB-featuremodules.md │ │ ├── IMDB-quickstart.md │ │ ├── IMDB-apidoc.md │ │ └── spectator-usage.md │ ├── static │ │ └── img │ │ │ ├── logo.png │ │ │ └── favicon.ico │ ├── babel.config.js │ ├── sidebars.js │ ├── blog │ │ ├── 2019-05-28-hola.md │ │ ├── 2019-05-29-hello-world.md │ │ └── 2019-05-30-welcome.md │ ├── src │ │ ├── pages │ │ │ ├── styles.module.css │ │ │ └── index.js │ │ └── css │ │ │ └── custom.css │ └── docusaurus.config.js ├── in-memory-db │ ├── src │ │ ├── services │ │ │ ├── index.ts │ │ │ └── in-memory-db.service.ts │ │ ├── v1 │ │ │ ├── services │ │ │ │ └── index.ts │ │ │ ├── factories │ │ │ │ ├── index.ts │ │ │ │ └── in-memory-db-service.factory.ts │ │ │ ├── common │ │ │ │ ├── index.ts │ │ │ │ ├── in-memory-db.constants.ts │ │ │ │ ├── in-memory-db.decorators.spec.ts │ │ │ │ ├── in-memory-db.utils.ts │ │ │ │ ├── in-memory-db.decorators.ts │ │ │ │ └── in-memory-db.utils.spec.ts │ │ │ ├── providers │ │ │ │ ├── index.ts │ │ │ │ ├── in-memory-db-for-root.providers.ts │ │ │ │ ├── in-memory-db-for-feature.providers.ts │ │ │ │ ├── in-memory-db-for-root.providers.spec.ts │ │ │ │ └── in-memory-db-for-feature.providers.spec.ts │ │ │ ├── interfaces │ │ │ │ ├── index.ts │ │ │ │ ├── in-memory-db-entity.ts │ │ │ │ └── in-memory-db-config.ts │ │ │ ├── controllers │ │ │ │ ├── index.ts │ │ │ │ ├── in-memory-db.controller.ts │ │ │ │ ├── in-memory-db-async.controller.ts │ │ │ │ ├── in-memory-db.controller.spec.ts │ │ │ │ └── in-memory-db-async.controller.spec.ts │ │ │ ├── index.ts │ │ │ ├── in-memory-db.module.ts │ │ │ └── in-memory-db.module.spec.ts │ │ ├── factories │ │ │ ├── index.ts │ │ │ └── in-memory-db-service.factory.ts │ │ ├── interfaces │ │ │ ├── in-memory-db-entity.ts │ │ │ ├── index.ts │ │ │ └── in-memory-db-config.ts │ │ ├── common │ │ │ ├── index.ts │ │ │ ├── in-memory-db.constants.ts │ │ │ ├── in-memory-db.utils.ts │ │ │ ├── in-memory-db.decorators.spec.ts │ │ │ ├── in-memory-db.decorators.ts │ │ │ └── in-memory-db.utils.spec.ts │ │ ├── providers │ │ │ ├── index.ts │ │ │ ├── in-memory-db-for-root.providers.ts │ │ │ ├── in-memory-db-for-feature.providers.ts │ │ │ ├── in-memory-db-for-root.providers.spec.ts │ │ │ └── in-memory-db-for-feature.providers.spec.ts │ │ ├── controllers │ │ │ ├── index.ts │ │ │ ├── in-memory-db.controller.ts │ │ │ ├── in-memory-db-async.controller.ts │ │ │ ├── in-memory-db.controller.spec.ts │ │ │ └── in-memory-db-async.controller.spec.ts │ │ ├── schematics │ │ │ ├── nest-add │ │ │ │ ├── schema.ts │ │ │ │ ├── schema.json │ │ │ │ ├── __snapshots__ │ │ │ │ │ └── index.spec.ts.snap │ │ │ │ ├── index.spec.ts │ │ │ │ └── index.ts │ │ │ ├── collection.json │ │ │ ├── mocks.ts │ │ │ └── utils │ │ │ │ ├── module.finder.ts │ │ │ │ └── change.ts │ │ ├── index.ts │ │ ├── in-memory-db.module.ts │ │ └── in-memory-db.module.spec.ts │ ├── .eslintrc.json │ ├── tsconfig.json │ ├── tsconfig.spec.json │ ├── jest.config.js │ ├── tsconfig.lib.json │ └── package.json ├── spectator │ ├── src │ │ ├── index.ts │ │ └── lib │ │ │ ├── types.ts │ │ │ ├── internal │ │ │ └── merge.ts │ │ │ ├── options.ts │ │ │ ├── testing-module.ts │ │ │ └── mock.ts │ ├── .eslintrc.json │ ├── tsconfig.json │ ├── tsconfig.lib.json │ ├── tsconfig.spec.json │ ├── jest.config.js │ ├── package.json │ └── README.md └── playground-e2e │ ├── .eslintrc.json │ ├── tsconfig.json │ ├── src │ ├── jest-e2e.json │ ├── app-product.e2e.spec.ts │ ├── app-customer.e2e.spec.ts │ ├── app-customer-product.e2e.spec.ts │ └── app.e2e.spec.ts │ ├── tsconfig.spec.json │ └── jest.config.js ├── tools ├── generators │ ├── .gitkeep │ └── new-addon │ │ ├── index.ts │ │ └── schema.json └── tsconfig.tools.json ├── renovate.json ├── .prettierrc ├── .prettierignore ├── .vscode └── extensions.json ├── jest.config.js ├── .editorconfig ├── jest.preset.js ├── .gitignore ├── tsconfig.base.json ├── .eslintrc.json ├── nx.json ├── LICENSE ├── migrations.json ├── package.json ├── CODE_OF_CONDUCT.md ├── README.md ├── API_V1.md ├── API.md ├── .github └── workflows │ └── build.yml └── workspace.json /packages/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tools/generators/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/playground/src/assets/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /packages/docs/docs/docs.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: docs 3 | title: Getting Started 4 | --- 5 | -------------------------------------------------------------------------------- /packages/in-memory-db/src/services/index.ts: -------------------------------------------------------------------------------- 1 | export * from './in-memory-db.service'; 2 | -------------------------------------------------------------------------------- /packages/in-memory-db/src/v1/services/index.ts: -------------------------------------------------------------------------------- 1 | export * from './in-memory-db.service'; 2 | -------------------------------------------------------------------------------- /packages/in-memory-db/src/factories/index.ts: -------------------------------------------------------------------------------- 1 | export * from './in-memory-db-service.factory'; 2 | -------------------------------------------------------------------------------- /packages/in-memory-db/src/v1/factories/index.ts: -------------------------------------------------------------------------------- 1 | export * from './in-memory-db-service.factory'; 2 | -------------------------------------------------------------------------------- /packages/spectator/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/testing-module'; 2 | export * from './lib/mock'; 3 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "all", 3 | "arrowParens": "always", 4 | "singleQuote": true 5 | } 6 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Add files here to ignore them from prettier formatting 2 | 3 | /dist 4 | /coverage 5 | .docusaurus/ 6 | -------------------------------------------------------------------------------- /packages/in-memory-db/src/interfaces/in-memory-db-entity.ts: -------------------------------------------------------------------------------- 1 | export interface InMemoryDBEntity { 2 | id: string; 3 | } 4 | -------------------------------------------------------------------------------- /packages/docs/static/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nestjs-addons/platform/HEAD/packages/docs/static/img/logo.png -------------------------------------------------------------------------------- /packages/docs/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')], 3 | }; 4 | -------------------------------------------------------------------------------- /packages/docs/static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nestjs-addons/platform/HEAD/packages/docs/static/img/favicon.ico -------------------------------------------------------------------------------- /packages/playground/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './app.controller'; 2 | export * from './user'; 3 | export * from './app.module'; 4 | -------------------------------------------------------------------------------- /packages/playground/src/user.ts: -------------------------------------------------------------------------------- 1 | export interface User { 2 | id: string; 3 | firstName: string; 4 | lastName: string; 5 | } 6 | -------------------------------------------------------------------------------- /packages/in-memory-db/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../.eslintrc", 3 | "rules": {}, 4 | "ignorePatterns": ["!**/*"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/playground/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../.eslintrc", 3 | "rules": {}, 4 | "ignorePatterns": ["!**/*"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/spectator/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../.eslintrc", 3 | "rules": {}, 4 | "ignorePatterns": ["!**/*"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/playground-e2e/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../.eslintrc", 3 | "rules": {}, 4 | "ignorePatterns": ["!**/*"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/in-memory-db/src/common/index.ts: -------------------------------------------------------------------------------- 1 | export * from './in-memory-db.decorators'; 2 | export { getInMemoryDBServiceToken } from './in-memory-db.utils'; 3 | -------------------------------------------------------------------------------- /packages/in-memory-db/src/providers/index.ts: -------------------------------------------------------------------------------- 1 | export * from './in-memory-db-for-root.providers'; 2 | export * from './in-memory-db-for-feature.providers'; 3 | -------------------------------------------------------------------------------- /packages/playground/src/product/product.ts: -------------------------------------------------------------------------------- 1 | export interface Product { 2 | id: string; 3 | name: string; 4 | cost: number; 5 | units: number; 6 | } 7 | -------------------------------------------------------------------------------- /packages/in-memory-db/src/v1/common/index.ts: -------------------------------------------------------------------------------- 1 | export * from './in-memory-db.decorators'; 2 | export { getInMemoryDBV1ServiceToken } from './in-memory-db.utils'; 3 | -------------------------------------------------------------------------------- /packages/in-memory-db/src/v1/providers/index.ts: -------------------------------------------------------------------------------- 1 | export * from './in-memory-db-for-root.providers'; 2 | export * from './in-memory-db-for-feature.providers'; 3 | -------------------------------------------------------------------------------- /packages/in-memory-db/src/interfaces/index.ts: -------------------------------------------------------------------------------- 1 | export { InMemoryDBConfig } from './in-memory-db-config'; 2 | export { InMemoryDBEntity } from './in-memory-db-entity'; 3 | -------------------------------------------------------------------------------- /packages/in-memory-db/src/common/in-memory-db.constants.ts: -------------------------------------------------------------------------------- 1 | export const IN_MEMORY_DB_SERVICE = 'InMemoryDBService'; 2 | export const IN_MEMORY_DB_CONFIG = 'InMemoryDBConfig'; 3 | -------------------------------------------------------------------------------- /packages/in-memory-db/src/v1/interfaces/index.ts: -------------------------------------------------------------------------------- 1 | export { InMemoryDBV1Config } from './in-memory-db-config'; 2 | export { InMemoryDBV1Entity } from './in-memory-db-entity'; 3 | -------------------------------------------------------------------------------- /packages/playground/src/customer/customer.ts: -------------------------------------------------------------------------------- 1 | import { User } from '../user'; 2 | 3 | export interface Customer extends User { 4 | title: string; 5 | company: string; 6 | } 7 | -------------------------------------------------------------------------------- /packages/in-memory-db/src/v1/common/in-memory-db.constants.ts: -------------------------------------------------------------------------------- 1 | export const IN_MEMORY_DB_V1_SERVICE = 'InMemoryDBV1Service'; 2 | export const IN_MEMORY_DB_V1_CONFIG = 'InMemoryDBV1Config'; 3 | -------------------------------------------------------------------------------- /packages/in-memory-db/src/controllers/index.ts: -------------------------------------------------------------------------------- 1 | export { InMemoryDBEntityAsyncController } from './in-memory-db-async.controller'; 2 | export { InMemoryDBEntityController } from './in-memory-db.controller'; 3 | -------------------------------------------------------------------------------- /packages/in-memory-db/src/v1/interfaces/in-memory-db-entity.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @deprecated since version 2.0.0, please use InMemoryDBEntity 3 | */ 4 | export interface InMemoryDBV1Entity { 5 | id: number; 6 | } 7 | -------------------------------------------------------------------------------- /packages/in-memory-db/src/v1/controllers/index.ts: -------------------------------------------------------------------------------- 1 | export { InMemoryDBV1EntityAsyncController } from './in-memory-db-async.controller'; 2 | export { InMemoryDBV1EntityController } from './in-memory-db.controller'; 3 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "nrwl.angular-console", 4 | "ms-vscode.vscode-typescript-tslint-plugin", 5 | "esbenp.prettier-vscode", 6 | "firsttris.vscode-jest-runner" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /packages/playground-e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.spec.json" 8 | } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | projects: [ 3 | '/packages/playground', 4 | '/packages/in-memory-db', 5 | '/packages/playground-e2e', 6 | '/packages/spectator', 7 | ], 8 | }; 9 | -------------------------------------------------------------------------------- /packages/playground/src/main.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from '@nestjs/core'; 2 | import { AppModule } from './'; 3 | 4 | async function bootstrap() { 5 | const app = await NestFactory.create(AppModule); 6 | await app.listen(3000); 7 | } 8 | bootstrap(); 9 | -------------------------------------------------------------------------------- /packages/playground-e2e/src/jest-e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["js", "json", "ts"], 3 | "rootDir": ".", 4 | "testEnvironment": "node", 5 | "testRegex": ".e2e-spec.ts$", 6 | "transform": { 7 | "^.+\\.(t|j)s$": "ts-jest" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/playground/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "include": ["**/*.spec.ts", "**/*.d.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/in-memory-db/src/schematics/nest-add/schema.ts: -------------------------------------------------------------------------------- 1 | export interface NestAddOptions { 2 | /** 3 | * Skip import parameter 4 | */ 5 | skipImport?: boolean; 6 | 7 | /** 8 | * The path to insert the module import. 9 | */ 10 | module?: string; 11 | } 12 | -------------------------------------------------------------------------------- /packages/playground-e2e/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "include": ["**/*.spec.ts", "**/*.d.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/in-memory-db/src/interfaces/in-memory-db-config.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * InMemoryDBConfig defines the config settings for InMemoryDBModule 3 | * 4 | * All properties should remain optional except featureName 5 | */ 6 | export interface InMemoryDBConfig { 7 | featureName: string; 8 | } 9 | -------------------------------------------------------------------------------- /packages/in-memory-db/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.lib.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /packages/playground/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.app.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /packages/spectator/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.lib.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /tools/generators/new-addon/index.ts: -------------------------------------------------------------------------------- 1 | import { chain, externalSchematic, Rule } from '@angular-devkit/schematics'; 2 | 3 | export default function (schema: any): Rule { 4 | return chain([ 5 | externalSchematic('@nrwl/nest', 'lib', { 6 | name: schema.name, 7 | }), 8 | ]); 9 | } 10 | -------------------------------------------------------------------------------- /tools/tsconfig.tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "../dist/out-tsc/tools", 5 | "rootDir": ".", 6 | "module": "commonjs", 7 | "target": "es5", 8 | "types": ["node"] 9 | }, 10 | "include": ["**/*.ts"] 11 | } 12 | -------------------------------------------------------------------------------- /packages/spectator/src/lib/types.ts: -------------------------------------------------------------------------------- 1 | type OptionalPropertyNames = { 2 | [K in keyof T]-?: undefined extends T[K] ? K : never; 3 | }[keyof T]; 4 | 5 | type OptionalProperties = Pick>; 6 | 7 | export type OptionalsRequired = Required> & Partial; 8 | -------------------------------------------------------------------------------- /packages/playground/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: '../../jest.preset.js', 3 | globals: { 4 | 'ts-jest': { 5 | tsConfig: '/tsconfig.spec.json', 6 | }, 7 | }, 8 | coverageDirectory: '../../coverage/packages/playground', 9 | displayName: 'playground', 10 | }; 11 | -------------------------------------------------------------------------------- /packages/spectator/src/lib/internal/merge.ts: -------------------------------------------------------------------------------- 1 | import { OptionalsRequired } from '../types'; 2 | 3 | /** 4 | * @internal 5 | */ 6 | export function merge( 7 | defaults: OptionalsRequired, 8 | overrides?: T, 9 | ): Required { 10 | return { ...defaults, ...overrides } as Required; 11 | } 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /packages/in-memory-db/src/v1/common/in-memory-db.decorators.spec.ts: -------------------------------------------------------------------------------- 1 | import { InjectInMemoryDBV1Service } from './in-memory-db.decorators'; 2 | 3 | describe('InjectInMemoryDBV1Service', () => { 4 | // TODO: Implement test of decorator 5 | it('should', () => { 6 | InjectInMemoryDBV1Service('feature'); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /packages/playground-e2e/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: '../../jest.preset.js', 3 | globals: { 4 | 'ts-jest': { 5 | tsConfig: '/tsconfig.spec.json', 6 | }, 7 | }, 8 | coverageDirectory: '../../coverage/packages/playground-e2e', 9 | displayName: 'playground-e2e', 10 | }; 11 | -------------------------------------------------------------------------------- /packages/playground/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "types": ["node"], 6 | "emitDecoratorMetadata": true, 7 | "target": "es2015" 8 | }, 9 | "exclude": ["**/*.spec.ts"], 10 | "include": ["**/*.ts"] 11 | } 12 | -------------------------------------------------------------------------------- /packages/spectator/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | "outDir": "../../dist/out-tsc", 6 | "declaration": true, 7 | "types": ["node", "jest"] 8 | }, 9 | "exclude": ["**/*.spec.ts"], 10 | "include": ["**/*.ts"] 11 | } 12 | -------------------------------------------------------------------------------- /packages/in-memory-db/src/common/in-memory-db.utils.ts: -------------------------------------------------------------------------------- 1 | import { IN_MEMORY_DB_SERVICE } from './in-memory-db.constants'; 2 | 3 | export function getInMemoryDBServiceToken(featureName?: string): string { 4 | return featureName && featureName !== IN_MEMORY_DB_SERVICE 5 | ? `${featureName}${IN_MEMORY_DB_SERVICE}` 6 | : IN_MEMORY_DB_SERVICE; 7 | } 8 | -------------------------------------------------------------------------------- /packages/in-memory-db/src/common/in-memory-db.decorators.spec.ts: -------------------------------------------------------------------------------- 1 | import { Inject } from '@nestjs/common'; 2 | import { InjectInMemoryDBService } from './in-memory-db.decorators'; 3 | 4 | describe('InjectInMemoryDBService', () => { 5 | // TODO: Implement test of decorator 6 | it('should', () => { 7 | InjectInMemoryDBService('feature'); 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /packages/in-memory-db/src/v1/common/in-memory-db.utils.ts: -------------------------------------------------------------------------------- 1 | import { IN_MEMORY_DB_V1_SERVICE } from './in-memory-db.constants'; 2 | 3 | export function getInMemoryDBV1ServiceToken(featureName?: string): string { 4 | return featureName && featureName !== IN_MEMORY_DB_V1_SERVICE 5 | ? `${featureName}${IN_MEMORY_DB_V1_SERVICE}` 6 | : IN_MEMORY_DB_V1_SERVICE; 7 | } 8 | -------------------------------------------------------------------------------- /packages/in-memory-db/src/common/in-memory-db.decorators.ts: -------------------------------------------------------------------------------- 1 | import { Inject } from '@nestjs/common'; 2 | import { getInMemoryDBServiceToken } from './in-memory-db.utils'; 3 | 4 | // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types 5 | export const InjectInMemoryDBService = (featureName: string) => 6 | Inject(getInMemoryDBServiceToken(featureName)); 7 | -------------------------------------------------------------------------------- /packages/in-memory-db/src/schematics/collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../../node_modules/@angular-devkit/schematics/collection-schema.json", 3 | "schematics": { 4 | "nest-add": { 5 | "description": "Schematics to add In-Memory DB Module.", 6 | "factory": "./nest-add", 7 | "schema": "./nest-add/schema.json" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /jest.preset.js: -------------------------------------------------------------------------------- 1 | const nxPreset = require('@nrwl/jest/preset'); 2 | module.exports = { 3 | ...nxPreset, 4 | testMatch: ['**/+(*.)+(spec|test).+(ts|js)?(x)'], 5 | transform: { 6 | '^.+\\.(ts|js|html)$': 'ts-jest', 7 | }, 8 | resolver: '@nrwl/jest/plugins/resolver', 9 | moduleFileExtensions: ['ts', 'js', 'html'], 10 | coverageReporters: ['html'], 11 | }; 12 | -------------------------------------------------------------------------------- /packages/in-memory-db/src/v1/interfaces/in-memory-db-config.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @deprecated since version 2.0.0, please use InMemoryDBConfig 3 | * 4 | * InMemoryDBV1Config defines the config settings for InMemoryDBV1Module 5 | * 6 | * All properties should remain optional except featureName 7 | */ 8 | export interface InMemoryDBV1Config { 9 | featureName: string; 10 | } 11 | -------------------------------------------------------------------------------- /packages/spectator/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "include": [ 9 | "**/*.spec.ts", 10 | "**/*.spec.tsx", 11 | "**/*.spec.js", 12 | "**/*.spec.jsx", 13 | "**/*.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /packages/in-memory-db/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "include": [ 9 | "**/*.spec.ts", 10 | "**/*.spec.tsx", 11 | "**/*.spec.js", 12 | "**/*.spec.jsx", 13 | "**/*.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /packages/playground/src/product/product.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { InMemoryDBModule } from '@nestjs-addons/in-memory-db'; 3 | import { ProductController } from './product.controller'; 4 | 5 | @Module({ 6 | imports: [InMemoryDBModule.forFeature('product')], 7 | controllers: [ProductController], 8 | }) 9 | export class ProductModule {} 10 | -------------------------------------------------------------------------------- /packages/in-memory-db/src/v1/common/in-memory-db.decorators.ts: -------------------------------------------------------------------------------- 1 | import { Inject } from '@nestjs/common'; 2 | import { getInMemoryDBV1ServiceToken } from './in-memory-db.utils'; 3 | 4 | export const InjectInMemoryDBV1Service = ( 5 | featureName: string, 6 | ): ((target: any, key: string | symbol, index?: number) => void) => { 7 | return Inject(getInMemoryDBV1ServiceToken(featureName)); 8 | }; 9 | -------------------------------------------------------------------------------- /packages/in-memory-db/src/v1/index.ts: -------------------------------------------------------------------------------- 1 | export { InjectInMemoryDBV1Service } from './common'; 2 | export { InMemoryDBV1Module } from './in-memory-db.module'; 3 | export { InMemoryDBV1Config, InMemoryDBV1Entity } from './interfaces'; 4 | export { InMemoryDBV1Service } from './services'; 5 | export { 6 | InMemoryDBV1EntityAsyncController, 7 | InMemoryDBV1EntityController, 8 | } from './controllers'; 9 | -------------------------------------------------------------------------------- /packages/playground/src/customer/customer.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { InMemoryDBModule } from '@nestjs-addons/in-memory-db'; 3 | import { CustomerController } from './customer.controller'; 4 | 5 | @Module({ 6 | imports: [InMemoryDBModule.forFeature('customer')], 7 | controllers: [CustomerController], 8 | providers: [], 9 | }) 10 | export class CustomerModule {} 11 | -------------------------------------------------------------------------------- /packages/spectator/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: '../../jest.preset.js', 3 | globals: { 4 | 'ts-jest': { 5 | tsConfig: '/tsconfig.spec.json', 6 | }, 7 | }, 8 | transform: { 9 | '^.+\\.[tj]sx?$': 'ts-jest', 10 | }, 11 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], 12 | coverageDirectory: '../../coverage/packages/spectator', 13 | displayName: 'spectator', 14 | }; 15 | -------------------------------------------------------------------------------- /packages/docs/sidebars.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | someSidebar: { 3 | NestJsAddons: ['docs'], 4 | InMemoryDB: [ 5 | 'in-memory-db-index', 6 | 'in-memory-db-installation', 7 | 'in-memory-db-quickstart', 8 | 'in-memory-db-featuremodules', 9 | 'in-memory-db-entitycontroller', 10 | 'in-memory-db-apidoc' 11 | ], 12 | Spectator: [ 13 | 'spectator-usage' 14 | ] 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /tools/generators/new-addon/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/schema", 3 | "id": "new-addon", 4 | "type": "object", 5 | "properties": { 6 | "name": { 7 | "type": "string", 8 | "description": "Addon name", 9 | "$default": { 10 | "$source": "argv", 11 | "index": 0 12 | }, 13 | "x-prompt": "What name would you like to use?" 14 | } 15 | }, 16 | "required": ["name"] 17 | } 18 | -------------------------------------------------------------------------------- /packages/docs/blog/2019-05-28-hola.md: -------------------------------------------------------------------------------- 1 | --- 2 | slug: hola 3 | title: Hola 4 | author: Gao Wei 5 | author_title: Docusaurus Core Team 6 | author_url: https://github.com/wgao19 7 | author_image_url: https://avatars1.githubusercontent.com/u/2055384?v=4 8 | tags: [hola, docusaurus] 9 | --- 10 | 11 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet 12 | -------------------------------------------------------------------------------- /packages/in-memory-db/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: '../../jest.preset.js', 3 | globals: { 4 | 'ts-jest': { 5 | tsConfig: '/tsconfig.spec.json', 6 | }, 7 | }, 8 | testEnvironment: 'node', 9 | transform: { 10 | '^.+\\.[tj]sx?$': 'ts-jest', 11 | }, 12 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'html'], 13 | coverageDirectory: '../../coverage/packages/in-memory-db', 14 | displayName: 'in-memory-db', 15 | }; 16 | -------------------------------------------------------------------------------- /packages/in-memory-db/src/schematics/nest-add/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/schema", 3 | "id": "SchematicsInMemoryDB", 4 | "title": "In-Memory DB Options Schema", 5 | "type": "object", 6 | "properties": { 7 | "skipImport": { 8 | "description": "Flag to skip the module import.", 9 | "default": false 10 | }, 11 | "module": { 12 | "type": "string", 13 | "description": "Allows specification of the declaring module.", 14 | "alias": "m" 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/docs/blog/2019-05-29-hello-world.md: -------------------------------------------------------------------------------- 1 | --- 2 | slug: hello-world 3 | title: Hello 4 | author: Endilie Yacop Sucipto 5 | author_title: Maintainer of Docusaurus 6 | author_url: https://github.com/endiliey 7 | author_image_url: https://avatars1.githubusercontent.com/u/17883920?s=460&v=4 8 | tags: [hello, docusaurus] 9 | --- 10 | 11 | Welcome to this blog. This blog is created with [**Docusaurus 2 alpha**](https://v2.docusaurus.io/). 12 | 13 | 14 | 15 | This is a test post. 16 | 17 | A whole bunch of other information. 18 | -------------------------------------------------------------------------------- /packages/docs/blog/2019-05-30-welcome.md: -------------------------------------------------------------------------------- 1 | --- 2 | slug: welcome 3 | title: Welcome 4 | author: Yangshun Tay 5 | author_title: Front End Engineer @ Facebook 6 | author_url: https://github.com/yangshun 7 | author_image_url: https://avatars0.githubusercontent.com/u/1315101?s=400&v=4 8 | tags: [facebook, hello, docusaurus] 9 | --- 10 | 11 | Blog features are powered by the blog plugin. Simply add files to the `blog` directory. It supports tags as well! 12 | 13 | Delete the whole directory if you don't want the blog features. As simple as that! 14 | -------------------------------------------------------------------------------- /packages/playground/src/customer-product/customer-product.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { InMemoryDBModule } from '@nestjs-addons/in-memory-db'; 3 | import { ProductController } from '../product/product.controller'; 4 | import { CustomerController } from '../customer/customer.controller'; 5 | 6 | @Module({ 7 | imports: [ 8 | InMemoryDBModule.forFeature('customer'), 9 | InMemoryDBModule.forFeature('product'), 10 | ], 11 | controllers: [ProductController, CustomerController], 12 | }) 13 | export class CustomerProductModule {} 14 | -------------------------------------------------------------------------------- /packages/in-memory-db/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | "declaration": true, 6 | "noImplicitAny": false, 7 | "removeComments": true, 8 | "noLib": false, 9 | "emitDecoratorMetadata": true, 10 | "experimentalDecorators": true, 11 | "target": "es6", 12 | "sourceMap": false, 13 | "outDir": "../../dist/out-tsc", 14 | "rootDir": "./src", 15 | "skipLibCheck": true 16 | }, 17 | "exclude": ["**/*.spec.ts", "**/mocks.ts"], 18 | "include": ["**/*.ts", "**/*.json"] 19 | } 20 | -------------------------------------------------------------------------------- /packages/in-memory-db/src/factories/in-memory-db-service.factory.ts: -------------------------------------------------------------------------------- 1 | import { InMemoryDBConfig, InMemoryDBEntity } from '../interfaces'; 2 | import { InMemoryDBService } from '../services'; 3 | 4 | export function inMemoryDBServiceFactory( 5 | featureConfig: Partial = {}, 6 | featureName?: string, 7 | ): () => InMemoryDBService { 8 | return () => 9 | new InMemoryDBService({ 10 | featureName: featureName 11 | ? featureName 12 | : featureConfig.featureName || 'root', 13 | ...featureConfig, 14 | }); 15 | } 16 | -------------------------------------------------------------------------------- /packages/in-memory-db/src/schematics/nest-add/__snapshots__/index.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`nest add function should add package to module 1`] = ` 4 | " 5 | import { Module } from '@nestjs/common'; 6 | import { AppController } from './app.controller'; 7 | import { AppService } from './app.service'; 8 | import { InMemoryDBModule } from '@nestjs-addons/in-memory-db'; 9 | 10 | @Module({ 11 | imports: [InMemoryDBModule.forRoot()], 12 | controllers: [AppController], 13 | providers: [AppService], 14 | }) 15 | export class AppModule {} 16 | " 17 | `; 18 | -------------------------------------------------------------------------------- /packages/playground/src/customer/customer.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller } from '@nestjs/common'; 2 | import { 3 | InMemoryDBService, 4 | InjectInMemoryDBService, 5 | InMemoryDBEntityController, 6 | } from '@nestjs-addons/in-memory-db'; 7 | import { Customer } from './customer'; 8 | 9 | @Controller('api/customers') 10 | export class CustomerController extends InMemoryDBEntityController { 11 | constructor( 12 | @InjectInMemoryDBService('customer') 13 | protected readonly inMemoryDBService: InMemoryDBService, 14 | ) { 15 | super(inMemoryDBService); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/in-memory-db/src/v1/factories/in-memory-db-service.factory.ts: -------------------------------------------------------------------------------- 1 | import { InMemoryDBV1Config, InMemoryDBV1Entity } from '../interfaces'; 2 | import { InMemoryDBV1Service } from '../services'; 3 | 4 | export function inMemoryDBV1ServiceFactory( 5 | featureConfig: Partial = {}, 6 | featureName?: string, 7 | ): () => InMemoryDBV1Service { 8 | return () => 9 | new InMemoryDBV1Service({ 10 | featureName: featureName 11 | ? featureName 12 | : featureConfig.featureName || 'root', 13 | ...featureConfig, 14 | }); 15 | } 16 | -------------------------------------------------------------------------------- /packages/docs/docs/IMDB-description.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: in-memory-db-index 3 | title: Introduction 4 | --- 5 | 6 | `@nestjs-addons/in-memory-db` provides a ridiculously simple, no configuration needed, way to create a simple in-memory database for use in your `nestjs` applications. You simply define an `interface` that extends the `interface InMemoryDBEntity`, inject the `InMemoryDBService` into your controllers and/or services, and immediately profit. The records are stored in-memory, as a singleton, for each interface, for the life of the service. 7 | 8 | This provides a great way to quickly get up and running with prototypes and mock backends. -------------------------------------------------------------------------------- /packages/in-memory-db/src/providers/in-memory-db-for-root.providers.ts: -------------------------------------------------------------------------------- 1 | import { FactoryProvider } from '@nestjs/common/interfaces'; 2 | 3 | import { inMemoryDBServiceFactory } from '../factories'; 4 | import { getInMemoryDBServiceToken } from '../common'; 5 | import { InMemoryDBConfig } from '../interfaces'; 6 | 7 | export function createInMemoryDBForRootProviders( 8 | featureConfig: Partial, 9 | ): FactoryProvider[] { 10 | const providers: FactoryProvider[] = [ 11 | { 12 | provide: getInMemoryDBServiceToken(), 13 | useFactory: inMemoryDBServiceFactory(featureConfig), 14 | }, 15 | ]; 16 | return providers; 17 | } 18 | -------------------------------------------------------------------------------- /packages/in-memory-db/src/schematics/mocks.ts: -------------------------------------------------------------------------------- 1 | export const mockMain = ` 2 | import { NestFactory } from '@nestjs/core'; 3 | import { AppModule } from './app.module'; 4 | 5 | async function bootstrap() { 6 | const app = await NestFactory.create(AppModule); 7 | await app.listen(3000); 8 | } 9 | bootstrap(); 10 | `; 11 | 12 | export const mockAppModule = ` 13 | import { Module } from '@nestjs/common'; 14 | import { AppController } from './app.controller'; 15 | import { AppService } from './app.service'; 16 | 17 | @Module({ 18 | imports: [], 19 | controllers: [AppController], 20 | providers: [AppService], 21 | }) 22 | export class AppModule {} 23 | `; 24 | -------------------------------------------------------------------------------- /packages/in-memory-db/src/common/in-memory-db.utils.spec.ts: -------------------------------------------------------------------------------- 1 | import { getInMemoryDBServiceToken } from './in-memory-db.utils'; 2 | 3 | describe('getInMemoryDBServiceToken', () => { 4 | test.each([ 5 | ['oneInMemoryDBService', 'one'], 6 | ['InMemoryDBService', ''], 7 | ['InMemoryDBService', null], 8 | ['InMemoryDBService', undefined], 9 | ])( 10 | 'should return %p token given input featureName of %p', 11 | (expectedToken: string, featureName: string) => { 12 | // act 13 | const actualToken = getInMemoryDBServiceToken(featureName); 14 | 15 | // assert 16 | expect(actualToken).toEqual(expectedToken); 17 | }, 18 | ); 19 | }); 20 | -------------------------------------------------------------------------------- /packages/in-memory-db/src/v1/providers/in-memory-db-for-root.providers.ts: -------------------------------------------------------------------------------- 1 | import { FactoryProvider } from '@nestjs/common/interfaces'; 2 | 3 | import { inMemoryDBV1ServiceFactory } from '../factories'; 4 | import { getInMemoryDBV1ServiceToken } from '../common'; 5 | import { InMemoryDBV1Config } from '../interfaces'; 6 | 7 | export function createInMemoryDBV1ForRootProviders( 8 | featureConfig: Partial, 9 | ): FactoryProvider[] { 10 | const providers: FactoryProvider[] = [ 11 | { 12 | provide: getInMemoryDBV1ServiceToken(), 13 | useFactory: inMemoryDBV1ServiceFactory(featureConfig), 14 | }, 15 | ]; 16 | return providers; 17 | } 18 | -------------------------------------------------------------------------------- /packages/in-memory-db/src/v1/common/in-memory-db.utils.spec.ts: -------------------------------------------------------------------------------- 1 | import { getInMemoryDBV1ServiceToken } from './in-memory-db.utils'; 2 | 3 | describe('getInMemoryDBV1ServiceToken', () => { 4 | test.each([ 5 | ['oneInMemoryDBV1Service', 'one'], 6 | ['InMemoryDBV1Service', ''], 7 | ['InMemoryDBV1Service', null], 8 | ['InMemoryDBV1Service', undefined], 9 | ])( 10 | 'should return %p token given input featureName of %p', 11 | (expectedToken: string, featureName: string) => { 12 | // act 13 | const actualToken = getInMemoryDBV1ServiceToken(featureName); 14 | 15 | // assert 16 | expect(actualToken).toEqual(expectedToken); 17 | }, 18 | ); 19 | }); 20 | -------------------------------------------------------------------------------- /packages/in-memory-db/src/providers/in-memory-db-for-feature.providers.ts: -------------------------------------------------------------------------------- 1 | import { InMemoryDBConfig } from '../interfaces'; 2 | import { getInMemoryDBServiceToken } from '../common'; 3 | import { inMemoryDBServiceFactory } from '../factories'; 4 | import { FactoryProvider } from '@nestjs/common/interfaces'; 5 | 6 | export function createInMemoryDBForFeatureProviders( 7 | featureName: string, 8 | featureConfig: Partial, 9 | ): FactoryProvider[] { 10 | const providers: FactoryProvider[] = [ 11 | { 12 | provide: getInMemoryDBServiceToken(featureName), 13 | useFactory: inMemoryDBServiceFactory(featureConfig, featureName), 14 | }, 15 | ]; 16 | return providers; 17 | } 18 | -------------------------------------------------------------------------------- /packages/docs/docs/IMDB-installation.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: in-memory-db-installation 3 | title: Installation 4 | --- 5 | 6 | ## Option 1 7 | 8 | ### With NPM 9 | 10 | ```bash 11 | $ npm i --save @nestjs-addons/in-memory-db 12 | ``` 13 | 14 | ### With Yarn 15 | 16 | ```bash 17 | $ yarn add @nestjs-addons/in-memory-db 18 | ``` 19 | 20 | ## Option 2 21 | 22 | The library support nest add command, you can run the below command to install and import the libary to root module. 23 | 24 | ```bash 25 | nest add @nestjs-addons/in-memory-db 26 | ``` 27 | 28 | ## Video Walkthrough 29 | 30 | [![](http://img.youtube.com/vi/eSx6nKDw5PQ/0.jpg)](http://www.youtube.com/watch?v=eSx6nKDw5PQ 'NestJS Addons - In Memory DB - Walkthrough') 31 | -------------------------------------------------------------------------------- /packages/in-memory-db/src/index.ts: -------------------------------------------------------------------------------- 1 | import 'reflect-metadata'; 2 | 3 | /** v1 exports */ 4 | export { 5 | InMemoryDBV1Config, 6 | InMemoryDBV1Entity, 7 | InMemoryDBV1EntityAsyncController, 8 | InMemoryDBV1EntityController, 9 | InMemoryDBV1Module, 10 | InMemoryDBV1Service, 11 | InjectInMemoryDBV1Service, 12 | } from './v1'; 13 | 14 | /** v2 exports */ 15 | export { InjectInMemoryDBService } from './common'; 16 | export { InMemoryDBModule } from './in-memory-db.module'; 17 | export { InMemoryDBConfig, InMemoryDBEntity } from './interfaces'; 18 | export { InMemoryDBService } from './services'; 19 | export { 20 | InMemoryDBEntityAsyncController, 21 | InMemoryDBEntityController, 22 | } from './controllers'; 23 | -------------------------------------------------------------------------------- /packages/in-memory-db/src/v1/providers/in-memory-db-for-feature.providers.ts: -------------------------------------------------------------------------------- 1 | import { InMemoryDBV1Config } from '../interfaces'; 2 | import { getInMemoryDBV1ServiceToken } from '../common'; 3 | import { inMemoryDBV1ServiceFactory } from '../factories'; 4 | import { FactoryProvider } from '@nestjs/common/interfaces'; 5 | 6 | export function createInMemoryDBV1ForFeatureProviders( 7 | featureName: string, 8 | featureConfig: Partial, 9 | ): FactoryProvider[] { 10 | const providers: FactoryProvider[] = [ 11 | { 12 | provide: getInMemoryDBV1ServiceToken(featureName), 13 | useFactory: inMemoryDBV1ServiceFactory(featureConfig, featureName), 14 | }, 15 | ]; 16 | return providers; 17 | } 18 | -------------------------------------------------------------------------------- /packages/spectator/src/lib/options.ts: -------------------------------------------------------------------------------- 1 | import { ModuleMetadata, Type } from '@nestjs/common/interfaces'; 2 | import { OptionalsRequired } from './types'; 3 | import { merge } from './internal/merge'; 4 | 5 | export interface BaseSpectatorModuleMetadata extends ModuleMetadata { 6 | mocks?: Type[]; 7 | } 8 | 9 | const defaultOptions: OptionalsRequired = { 10 | imports: [], 11 | controllers: [], 12 | providers: [], 13 | exports: [], 14 | mocks: [], 15 | }; 16 | 17 | /** 18 | * @internal 19 | */ 20 | export function getSpectatorDefaultOptions( 21 | overrides?: BaseSpectatorModuleMetadata, 22 | ): Required { 23 | return merge(defaultOptions, overrides); 24 | } 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | 8 | # dependencies 9 | /node_modules 10 | 11 | # IDEs and editors 12 | /.idea 13 | .project 14 | .classpath 15 | .c9/ 16 | *.launch 17 | .settings/ 18 | *.sublime-workspace 19 | 20 | # IDE - VSCode 21 | .vscode/* 22 | !.vscode/settings.json 23 | !.vscode/tasks.json 24 | !.vscode/launch.json 25 | !.vscode/extensions.json 26 | 27 | # misc 28 | /.sass-cache 29 | /connect.lock 30 | /coverage 31 | /libpeerconnection.log 32 | npm-debug.log 33 | yarn-error.log 34 | testem.log 35 | /typings 36 | 37 | # System Files 38 | .DS_Store 39 | Thumbs.db 40 | 41 | # Generated Docusaurus files 42 | .docusaurus/ 43 | .cache-loader/ 44 | -------------------------------------------------------------------------------- /packages/playground/src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { InMemoryDBModule } from '@nestjs-addons/in-memory-db'; 3 | import { AppController } from './app.controller'; 4 | import { ProductController } from './product/product.controller'; 5 | import { ProductModule } from './product/product.module'; 6 | import { CustomerProductModule } from './customer-product/customer-product.module'; 7 | 8 | @Module({ 9 | imports: [ 10 | InMemoryDBModule.forFeature('product'), 11 | InMemoryDBModule.forFeature('customer'), 12 | InMemoryDBModule.forRoot(), 13 | ProductModule, 14 | CustomerProductModule, 15 | ], 16 | controllers: [AppController, ProductController], 17 | providers: [], 18 | }) 19 | export class AppModule {} 20 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "rootDir": ".", 5 | "sourceMap": true, 6 | "declaration": false, 7 | "moduleResolution": "node", 8 | "emitDecoratorMetadata": true, 9 | "experimentalDecorators": true, 10 | "importHelpers": true, 11 | "target": "es2015", 12 | "module": "esnext", 13 | "typeRoots": ["node_modules/@types"], 14 | "lib": ["es2017", "dom"], 15 | "skipLibCheck": true, 16 | "skipDefaultLibCheck": true, 17 | "baseUrl": ".", 18 | "paths": { 19 | "@nestjs-addons/in-memory-db": ["packages/in-memory-db/src/index.ts"], 20 | "@nestjs-addons/spectator": ["packages/spectator/src/index.ts"] 21 | } 22 | }, 23 | "exclude": ["node_modules", "tmp"] 24 | } 25 | -------------------------------------------------------------------------------- /packages/spectator/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@nestjs-addons/spectator", 3 | "version": "0.0.11", 4 | "description": "Auto-mocking for Nestjs providers", 5 | "author": "Jay Bell ", 6 | "homepage": "https://www.npmjs.com/package/nest-spectator", 7 | "license": "MIT", 8 | "main": "lib/index.js", 9 | "typings": "lib/index.d.ts", 10 | "directories": { 11 | "lib": "lib", 12 | "test": "__tests__" 13 | }, 14 | "files": [ 15 | "lib" 16 | ], 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/nestjs-addons/platform" 20 | }, 21 | "bugs": { 22 | "url": "https://github.com/nestjs-addons/platform" 23 | }, 24 | "keywords": [ 25 | "nestjs", 26 | "nest", 27 | "typescript", 28 | "testing", 29 | "jest" 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /packages/docs/src/pages/styles.module.css: -------------------------------------------------------------------------------- 1 | /* stylelint-disable docusaurus/copyright-header */ 2 | /** 3 | * CSS files with the .module.css suffix will be treated as CSS modules 4 | * and scoped locally. 5 | */ 6 | 7 | .heroBanner { 8 | padding: 4rem 0; 9 | text-align: center; 10 | position: relative; 11 | overflow: hidden; 12 | } 13 | 14 | @media screen and (max-width: 966px) { 15 | .heroBanner { 16 | padding: 2rem; 17 | } 18 | } 19 | 20 | .buttons { 21 | display: flex; 22 | align-items: center; 23 | justify-content: center; 24 | } 25 | 26 | .features { 27 | display: flex; 28 | align-items: center; 29 | padding: 2rem 0; 30 | width: 100%; 31 | } 32 | 33 | .contributors { 34 | display: flex; 35 | align-items: center; 36 | padding: 2rem 0; 37 | width: 100%; 38 | } 39 | 40 | .featureImage { 41 | height: 200px; 42 | width: 200px; 43 | } 44 | -------------------------------------------------------------------------------- /packages/in-memory-db/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@nestjs-addons/in-memory-db", 3 | "version": "3.0.3", 4 | "license": "MIT", 5 | "description": "Simple In-Memory DB Service for NestJS projects", 6 | "author": "Wes Grimes", 7 | "homepage": "https://github.com/nestjs-addons/platform", 8 | "repository": "https://github.com/nestjs-addons/platform", 9 | "schematics": "./dist/schematics/collection.json", 10 | "publishConfig": { 11 | "access": "public" 12 | }, 13 | "peerDependencies": { 14 | "@nestjs/common": "^6.7.0 || ^7.0.0", 15 | "@nestjs/core": "^6.7.0 || ^7.0.0", 16 | "reflect-metadata": "^0.1.12", 17 | "rxjs": "^6.0.0" 18 | }, 19 | "keywords": [ 20 | "nestjs", 21 | "nest", 22 | "api", 23 | "in-memory db", 24 | "in-memory", 25 | "prototyping", 26 | "db", 27 | "crud", 28 | "rest", 29 | "typescript", 30 | "javascript" 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /packages/spectator/src/lib/testing-module.ts: -------------------------------------------------------------------------------- 1 | import { ModuleMetadata } from '@nestjs/common/interfaces'; 2 | import { 3 | BaseSpectatorModuleMetadata, 4 | getSpectatorDefaultOptions, 5 | } from './options'; 6 | import { TestingModuleBuilder } from '@nestjs/testing/testing-module.builder'; 7 | import { mockProvider } from './mock'; 8 | import { Test } from '@nestjs/testing'; 9 | 10 | export function createTestingModuleFactory( 11 | metadata: BaseSpectatorModuleMetadata, 12 | ): TestingModuleBuilder { 13 | const options = getSpectatorDefaultOptions(metadata); 14 | 15 | const moduleMetadata: ModuleMetadata = { 16 | imports: options.imports, 17 | controllers: options.controllers, 18 | providers: [ 19 | ...options.providers, 20 | ...options.mocks.map((mock) => mockProvider(mock)), 21 | ], 22 | exports: options.exports, 23 | }; 24 | 25 | return Test.createTestingModule(moduleMetadata); 26 | } 27 | -------------------------------------------------------------------------------- /packages/docs/src/css/custom.css: -------------------------------------------------------------------------------- 1 | /* stylelint-disable docusaurus/copyright-header */ 2 | /** 3 | * Any CSS included here will be global. The classic template 4 | * bundles Infima by default. Infima is a CSS framework designed to 5 | * work well for content-centric websites. 6 | */ 7 | 8 | /* You can override the default Infima variables here. */ 9 | :root { 10 | --ifm-color-primary: #c8003f; 11 | --ifm-color-primary-dark: rgb(200, 0, 63); 12 | --ifm-color-primary-darker: rgb(171, 0, 48); 13 | --ifm-color-primary-darkest: rgb(128, 0, 36); 14 | --ifm-color-primary-light: rgb(128, 0, 36); 15 | --ifm-color-primary-lighter: rgb(227, 2, 65); 16 | --ifm-color-primary-lightest: rgb(255, 79, 128); 17 | --ifm-code-font-size: 95%; 18 | } 19 | 20 | .docusaurus-highlight-code-line { 21 | background-color: rgb(72, 77, 91); 22 | display: block; 23 | margin: 0 calc(-1 * var(--ifm-pre-padding)); 24 | padding: 0 var(--ifm-pre-padding); 25 | } 26 | -------------------------------------------------------------------------------- /packages/in-memory-db/src/schematics/utils/module.finder.ts: -------------------------------------------------------------------------------- 1 | import { join, Path, PathFragment } from '@angular-devkit/core'; 2 | import { DirEntry, Tree } from '@angular-devkit/schematics'; 3 | 4 | export interface FindOptions { 5 | name?: string; 6 | path: Path; 7 | kind?: string; 8 | } 9 | 10 | export class ModuleFinder { 11 | constructor(private tree: Tree) {} 12 | 13 | public find(options: FindOptions): Path | null { 14 | const generatedDirectoryPath: Path = options.path; 15 | const generatedDirectory: DirEntry = this.tree.getDir( 16 | generatedDirectoryPath, 17 | ); 18 | return this.findIn(generatedDirectory); 19 | } 20 | 21 | private findIn(directory: DirEntry): Path | null { 22 | if (!directory) { 23 | return null; 24 | } 25 | const moduleFilename: PathFragment = directory.subfiles.find((filename) => 26 | /\.module\.(t|j)s/.test(filename), 27 | ); 28 | return moduleFilename !== undefined 29 | ? join(directory.path, moduleFilename.valueOf()) 30 | : this.findIn(directory.parent); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "ignorePatterns": ["**/*"], 4 | "plugins": ["@nrwl/nx"], 5 | "overrides": [ 6 | { 7 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 8 | "parserOptions": { 9 | "project": "./tsconfig.*?.json" 10 | }, 11 | "rules": { 12 | "@nrwl/nx/enforce-module-boundaries": [ 13 | "error", 14 | { 15 | "enforceBuildableLibDependency": true, 16 | "allow": ["playground"], 17 | "depConstraints": [ 18 | { 19 | "sourceTag": "*", 20 | "onlyDependOnLibsWithTags": ["*"] 21 | } 22 | ] 23 | } 24 | ] 25 | } 26 | }, 27 | { 28 | "files": ["*.ts", "*.tsx"], 29 | "extends": ["plugin:@nrwl/nx/typescript"], 30 | "parserOptions": { 31 | "project": "./tsconfig.*?.json" 32 | }, 33 | "rules": {} 34 | }, 35 | { 36 | "files": ["*.js", "*.jsx"], 37 | "extends": ["plugin:@nrwl/nx/javascript"], 38 | "rules": {} 39 | } 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /packages/in-memory-db/src/in-memory-db.module.ts: -------------------------------------------------------------------------------- 1 | import { DynamicModule, Module } from '@nestjs/common'; 2 | 3 | import { InMemoryDBConfig } from './interfaces'; 4 | import { InMemoryDBService } from './services'; 5 | import { 6 | createInMemoryDBForRootProviders, 7 | createInMemoryDBForFeatureProviders, 8 | } from './providers'; 9 | @Module({ 10 | providers: [InMemoryDBService], 11 | exports: [InMemoryDBService], 12 | }) 13 | export class InMemoryDBModule { 14 | public static forRoot(config: Partial = {}): DynamicModule { 15 | const providers = createInMemoryDBForRootProviders(config); 16 | return { 17 | module: InMemoryDBModule, 18 | providers, 19 | exports: providers, 20 | }; 21 | } 22 | 23 | public static forFeature( 24 | featureName: string, 25 | config: Partial = {}, 26 | ): DynamicModule { 27 | const providers = createInMemoryDBForFeatureProviders(featureName, config); 28 | return { 29 | module: InMemoryDBModule, 30 | providers, 31 | exports: providers, 32 | }; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /nx.json: -------------------------------------------------------------------------------- 1 | { 2 | "npmScope": "nestjs-addons", 3 | "affected": { 4 | "defaultBase": "master" 5 | }, 6 | "implicitDependencies": { 7 | "workspace.json": "*", 8 | "package.json": { 9 | "dependencies": "*", 10 | "devDependencies": "*" 11 | }, 12 | "tsconfig.base.json": "*", 13 | "tslint.json": "*", 14 | "nx.json": "*", 15 | ".eslintrc.json": "*" 16 | }, 17 | "tasksRunnerOptions": { 18 | "default": { 19 | "runner": "@nrwl/workspace/tasks-runners/default", 20 | "options": { 21 | "cacheableOperations": ["build", "lint", "test", "e2e"] 22 | } 23 | } 24 | }, 25 | "projects": { 26 | "playground": { 27 | "tags": [] 28 | }, 29 | "in-memory-db": { 30 | "tags": [] 31 | }, 32 | "playground-e2e": { 33 | "tags": [], 34 | "implicitDependencies": ["playground"] 35 | }, 36 | "spectator": { 37 | "tags": [] 38 | }, 39 | "docs": { 40 | "tags": [] 41 | } 42 | }, 43 | "workspaceLayout": { 44 | "appsDir": "packages", 45 | "libsDir": "packages" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 NestJS Addons 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/in-memory-db/src/providers/in-memory-db-for-root.providers.spec.ts: -------------------------------------------------------------------------------- 1 | import { FactoryProvider } from '@nestjs/common/interfaces'; 2 | 3 | import { getInMemoryDBServiceToken } from '../common/in-memory-db.utils'; 4 | import { createInMemoryDBForRootProviders } from './in-memory-db-for-root.providers'; 5 | import { InMemoryDBConfig } from '../interfaces'; 6 | import { inMemoryDBServiceFactory } from '../factories'; 7 | 8 | describe('createInMemoryDBForRootProviders', () => { 9 | test('returns correct providers array given featureName and featureConfig', () => { 10 | // arrange 11 | const inputFeatureConfig: Partial = {}; 12 | 13 | const expectedProviders: Array> = [ 14 | { 15 | provide: getInMemoryDBServiceToken(), 16 | useFactory: inMemoryDBServiceFactory(inputFeatureConfig), 17 | }, 18 | ]; 19 | 20 | // act 21 | const actualProviders = createInMemoryDBForRootProviders( 22 | inputFeatureConfig, 23 | ); 24 | 25 | // assert 26 | expect(actualProviders[0].provide).toEqual(expectedProviders[0].provide); 27 | expect(actualProviders[0].useFactory()).toEqual( 28 | expectedProviders[0].useFactory(), 29 | ); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /packages/in-memory-db/src/v1/providers/in-memory-db-for-root.providers.spec.ts: -------------------------------------------------------------------------------- 1 | import { FactoryProvider } from '@nestjs/common/interfaces'; 2 | 3 | import { getInMemoryDBV1ServiceToken } from '../common/in-memory-db.utils'; 4 | import { createInMemoryDBV1ForRootProviders } from './in-memory-db-for-root.providers'; 5 | import { InMemoryDBV1Config } from '../interfaces'; 6 | import { inMemoryDBV1ServiceFactory } from '../factories'; 7 | 8 | describe('createInMemoryDBV1ForRootProviders', () => { 9 | test('returns correct providers array given featureName and featureConfig', () => { 10 | // arrange 11 | const inputFeatureConfig: Partial = {}; 12 | 13 | const expectedProviders: Array> = [ 14 | { 15 | provide: getInMemoryDBV1ServiceToken(), 16 | useFactory: inMemoryDBV1ServiceFactory(inputFeatureConfig), 17 | }, 18 | ]; 19 | 20 | // act 21 | const actualProviders = createInMemoryDBV1ForRootProviders( 22 | inputFeatureConfig, 23 | ); 24 | 25 | // assert 26 | expect(actualProviders[0].provide).toEqual(expectedProviders[0].provide); 27 | expect(actualProviders[0].useFactory()).toEqual( 28 | expectedProviders[0].useFactory(), 29 | ); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /packages/playground/src/product/product.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Controller, 3 | Get, 4 | Param, 5 | Post, 6 | Body, 7 | Put, 8 | Delete, 9 | } from '@nestjs/common'; 10 | import { 11 | InMemoryDBService, 12 | InjectInMemoryDBService, 13 | } from '@nestjs-addons/in-memory-db'; 14 | import { Product } from './product'; 15 | 16 | @Controller('api/') 17 | export class ProductController { 18 | constructor( 19 | @InjectInMemoryDBService('product') 20 | private readonly inMemoryDBService: InMemoryDBService, 21 | ) {} 22 | 23 | @Get('products') 24 | getProducts(): Product[] { 25 | return this.inMemoryDBService.getAll(); 26 | } 27 | 28 | @Get('products/:id') 29 | getProduct(@Param('id') id: string): Product { 30 | return this.inMemoryDBService.get(id); 31 | } 32 | 33 | @Post('products') 34 | createProduct(@Body() product: Product): Product { 35 | return this.inMemoryDBService.create(product); 36 | } 37 | 38 | @Put('products/:id') 39 | updateProduct(@Body() product: Product): void { 40 | return this.inMemoryDBService.update(product); 41 | } 42 | 43 | @Delete('products/:id') 44 | deleteProduct(@Param('id') id: string): void { 45 | return this.inMemoryDBService.delete(id); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /packages/in-memory-db/src/v1/in-memory-db.module.ts: -------------------------------------------------------------------------------- 1 | import { DynamicModule, Module } from '@nestjs/common'; 2 | 3 | import { InMemoryDBV1Config } from './interfaces'; 4 | import { InMemoryDBV1Service } from './services'; 5 | import { 6 | createInMemoryDBV1ForRootProviders, 7 | createInMemoryDBV1ForFeatureProviders, 8 | } from './providers'; 9 | 10 | /** 11 | * @deprecated since version 2.0.0, please use InMemoryDBModule 12 | */ 13 | @Module({ 14 | providers: [InMemoryDBV1Service], 15 | exports: [InMemoryDBV1Service], 16 | }) 17 | export class InMemoryDBV1Module { 18 | public static forRoot( 19 | config: Partial = {}, 20 | ): DynamicModule { 21 | const providers = createInMemoryDBV1ForRootProviders(config); 22 | return { 23 | module: InMemoryDBV1Module, 24 | providers, 25 | exports: providers, 26 | }; 27 | } 28 | 29 | public static forFeature( 30 | featureName: string, 31 | config: Partial = {}, 32 | ): DynamicModule { 33 | const providers = createInMemoryDBV1ForFeatureProviders( 34 | featureName, 35 | config, 36 | ); 37 | return { 38 | module: InMemoryDBV1Module, 39 | providers, 40 | exports: providers, 41 | }; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /packages/docs/docs/IMDB-entitycontroller.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: in-memory-db-entitycontroller 3 | title: Entity Controller 4 | --- 5 | 6 | In order to prevent code duplication and boilerplate for each controller, we have created two base entity controllers `InMemoryDBEntityController` and `InMemoryDBEntityAsyncController`. This allows you to quickly provide endpoints to make requests without having to manually implement each action. 7 | 8 | To use the controllers, simply create a new controller and extend it with one of the provided base controllers. 9 | 10 | ```typescript 11 | @Controller('api/users') 12 | class UsersController extends InMemoryDBEntityController { 13 | constructor(protected dbService: InMemoryDBService) { 14 | super(dbService); 15 | } 16 | } 17 | ``` 18 | 19 | In order to have an Entity Controller use a feature-specific instance of the service, use the decorator `InjectInMemoryDBService` in the controller's provided by this library as shown below: 20 | 21 | ```typescript 22 | @Controller('api/users') 23 | class UsersController extends InMemoryDBEntityController { 24 | constructor( 25 | @InjectInMemoryDBService('customer') 26 | protected readonly inMemoryDBService: InMemoryDBService, 27 | ) { 28 | super(inMemoryDBService); 29 | } 30 | } 31 | ``` -------------------------------------------------------------------------------- /packages/in-memory-db/src/schematics/nest-add/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { HostTree } from '@angular-devkit/schematics'; 2 | import { 3 | SchematicTestRunner, 4 | UnitTestTree, 5 | } from '@angular-devkit/schematics/testing'; 6 | import * as path from 'path'; 7 | import { mockAppModule, mockMain } from '../mocks'; 8 | 9 | const collectionPath = path.join(__dirname, '../collection.json'); 10 | 11 | describe('nest add function', () => { 12 | let tree: UnitTestTree; 13 | let runner: SchematicTestRunner; 14 | 15 | beforeEach(() => { 16 | tree = new UnitTestTree(new HostTree()); 17 | tree.create('/package.json', JSON.stringify({})); 18 | tree.create( 19 | '/nest-cli.json', 20 | JSON.stringify({ 21 | language: 'ts', 22 | collection: '@nestjs/schematics', 23 | sourceRoot: 'src', 24 | }), 25 | ); 26 | tree.create('/src/main.ts', mockMain); 27 | tree.create('/src/app.module.ts', mockAppModule); 28 | runner = new SchematicTestRunner('schematics', collectionPath); 29 | }); 30 | 31 | it('should add package to module', async (done) => { 32 | const ngAddTree = await runner.runSchematicAsync('nest-add', '', tree).toPromise(); 33 | const module = ngAddTree.readContent('/src/app.module.ts'); 34 | expect(module).toMatchSnapshot(); 35 | 36 | done(); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /packages/in-memory-db/src/providers/in-memory-db-for-feature.providers.spec.ts: -------------------------------------------------------------------------------- 1 | import { FactoryProvider } from '@nestjs/common/interfaces'; 2 | 3 | import { getInMemoryDBServiceToken } from '../common/in-memory-db.utils'; 4 | import { createInMemoryDBForFeatureProviders } from './in-memory-db-for-feature.providers'; 5 | import { InMemoryDBConfig } from '../interfaces'; 6 | import { inMemoryDBServiceFactory } from '../factories'; 7 | 8 | describe('createInMemoryDBForFeatureProviders', () => { 9 | test('returns correct providers array given featureName and featureConfig', () => { 10 | // arrange 11 | const inputFeatureName = 'myFeature'; 12 | const inputFeatureConfig: Partial = {}; 13 | 14 | const expectedProviders: Array> = [ 15 | { 16 | provide: getInMemoryDBServiceToken(inputFeatureName), 17 | useFactory: inMemoryDBServiceFactory( 18 | inputFeatureConfig, 19 | inputFeatureName, 20 | ), 21 | }, 22 | ]; 23 | 24 | // act 25 | const actualProviders = createInMemoryDBForFeatureProviders( 26 | inputFeatureName, 27 | inputFeatureConfig, 28 | ); 29 | 30 | // assert 31 | expect(actualProviders[0].provide).toEqual(expectedProviders[0].provide); 32 | expect(actualProviders[0].useFactory()).toEqual( 33 | expectedProviders[0].useFactory(), 34 | ); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /packages/in-memory-db/src/v1/providers/in-memory-db-for-feature.providers.spec.ts: -------------------------------------------------------------------------------- 1 | import { FactoryProvider } from '@nestjs/common/interfaces'; 2 | 3 | import { getInMemoryDBV1ServiceToken } from '../common/in-memory-db.utils'; 4 | import { createInMemoryDBV1ForFeatureProviders } from './in-memory-db-for-feature.providers'; 5 | import { InMemoryDBV1Config } from '../interfaces'; 6 | import { inMemoryDBV1ServiceFactory } from '../factories'; 7 | 8 | describe('createInMemoryDBV1ForFeatureProviders', () => { 9 | test('returns correct providers array given featureName and featureConfig', () => { 10 | // arrange 11 | const inputFeatureName = 'myFeature'; 12 | const inputFeatureConfig: Partial = {}; 13 | 14 | const expectedProviders: Array> = [ 15 | { 16 | provide: getInMemoryDBV1ServiceToken(inputFeatureName), 17 | useFactory: inMemoryDBV1ServiceFactory( 18 | inputFeatureConfig, 19 | inputFeatureName, 20 | ), 21 | }, 22 | ]; 23 | 24 | // act 25 | const actualProviders = createInMemoryDBV1ForFeatureProviders( 26 | inputFeatureName, 27 | inputFeatureConfig, 28 | ); 29 | 30 | // assert 31 | expect(actualProviders[0].provide).toEqual(expectedProviders[0].provide); 32 | expect(actualProviders[0].useFactory()).toEqual( 33 | expectedProviders[0].useFactory(), 34 | ); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /packages/in-memory-db/src/in-memory-db.module.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test } from '@nestjs/testing'; 2 | import { InMemoryDBModule } from './in-memory-db.module'; 3 | 4 | describe('InMemoryDBModule', () => { 5 | describe('forRoot', () => { 6 | beforeEach(async () => { 7 | await Test.createTestingModule({ 8 | imports: [InMemoryDBModule.forRoot({})], 9 | }).compile(); 10 | }); 11 | 12 | it('should create', () => { 13 | expect(InMemoryDBModule).toBeDefined(); 14 | }); 15 | }); 16 | describe('forRoot - no object', () => { 17 | beforeEach(async () => { 18 | await Test.createTestingModule({ 19 | imports: [InMemoryDBModule.forRoot()], 20 | }).compile(); 21 | }); 22 | 23 | it('should create', () => { 24 | expect(InMemoryDBModule).toBeDefined(); 25 | }); 26 | }); 27 | describe('forFeature', () => { 28 | beforeEach(async () => { 29 | await Test.createTestingModule({ 30 | imports: [InMemoryDBModule.forFeature('feature', {})], 31 | }).compile(); 32 | }); 33 | 34 | it('should create', () => { 35 | expect(InMemoryDBModule).toBeDefined(); 36 | }); 37 | }); 38 | describe('forFeature - no object', () => { 39 | beforeEach(async () => { 40 | await Test.createTestingModule({ 41 | imports: [InMemoryDBModule.forFeature('feature')], 42 | }).compile(); 43 | }); 44 | 45 | it('should create', () => { 46 | expect(InMemoryDBModule).toBeDefined(); 47 | }); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /packages/in-memory-db/src/v1/in-memory-db.module.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test } from '@nestjs/testing'; 2 | import { InMemoryDBV1Module } from './in-memory-db.module'; 3 | 4 | describe('InMemoryDBV1Module', () => { 5 | describe('forRoot', () => { 6 | beforeEach(async () => { 7 | await Test.createTestingModule({ 8 | imports: [InMemoryDBV1Module.forRoot({})], 9 | }).compile(); 10 | }); 11 | 12 | it('should create', () => { 13 | expect(InMemoryDBV1Module).toBeDefined(); 14 | }); 15 | }); 16 | describe('forRoot - no object', () => { 17 | beforeEach(async () => { 18 | await Test.createTestingModule({ 19 | imports: [InMemoryDBV1Module.forRoot()], 20 | }).compile(); 21 | }); 22 | 23 | it('should create', () => { 24 | expect(InMemoryDBV1Module).toBeDefined(); 25 | }); 26 | }); 27 | describe('forFeature', () => { 28 | beforeEach(async () => { 29 | await Test.createTestingModule({ 30 | imports: [InMemoryDBV1Module.forFeature('feature', {})], 31 | }).compile(); 32 | }); 33 | 34 | it('should create', () => { 35 | expect(InMemoryDBV1Module).toBeDefined(); 36 | }); 37 | }); 38 | describe('forFeature - no object', () => { 39 | beforeEach(async () => { 40 | await Test.createTestingModule({ 41 | imports: [InMemoryDBV1Module.forFeature('feature')], 42 | }).compile(); 43 | }); 44 | 45 | it('should create', () => { 46 | expect(InMemoryDBV1Module).toBeDefined(); 47 | }); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /packages/docs/docs/IMDB-featuremodules.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: in-memory-db-featuremodules 3 | title: Feature Modules 4 | --- 5 | 6 | ## Registering Multiple Instances using `forFeature` 7 | 8 | Registering multiple instances for specific feature modules is super simple. Each feature module is guaranteed isolated to that feature. In order to get up and running you need to do the following: 9 | 10 | ### Registering a forFeature InMemoryDBService 11 | 12 | For each feature module(s), do the following: 13 | 14 | ```typescript 15 | // feature-one.module.ts 16 | 17 | import { Module } from '@nestjs/common'; 18 | import { InMemoryDBModule } from '@nestjs-addons/in-memory-db'; 19 | ... 20 | 21 | @Module({ 22 | ... 23 | imports: [InMemoryDBModule.forFeature('one', {})], 24 | ... 25 | }) 26 | export class FeatureOneModule {} 27 | ``` 28 | 29 | As you can see we: 30 | 31 | - Imported `InMemoryDBModule` from `@nestjs-addons/in-memory-db` 32 | - Added `InMemoryDBModule` to the `imports` array in the `@Module` of your choice 33 | - Added the `forFeature` method call passing `one` as the feature name 34 | 35 | ### Using the Feature Instance 36 | 37 | If you would like to use the feature-specific instance, make use of the included `@InjectInMemoryDBService` decorator: 38 | 39 | ```typescript 40 | @Controller({...}) 41 | export class FeatureOneController { 42 | constructor(@InjectInMemoryDBService('one') private oneService: InMemoryDBService) {} 43 | ... 44 | @Get() 45 | getAll(): OneEntity[] { 46 | return this.oneService.getAll(); 47 | } 48 | } 49 | ``` 50 | 51 | Using this decorator ensures that the correct instance is injected. 52 | -------------------------------------------------------------------------------- /migrations.json: -------------------------------------------------------------------------------- 1 | { 2 | "migrations": [ 3 | { 4 | "version": "11.0.0-beta.3", 5 | "description": "Update the decoration script when using Angular CLI", 6 | "factory": "./src/migrations/update-11-0-0/update-decorate-angular-cli", 7 | "package": "@nrwl/workspace", 8 | "name": "update-decorate-angular-cli" 9 | }, 10 | { 11 | "version": "11.0.0-beta.3", 12 | "description": "Update the @types/node package", 13 | "factory": "./src/migrations/update-11-0-0/update-node-types", 14 | "package": "@nrwl/workspace", 15 | "name": "update-node-types" 16 | }, 17 | { 18 | "version": "11.0.0-beta.3", 19 | "description": "Rename tools/schematics into tools/generators", 20 | "factory": "./src/migrations/update-11-0-0/rename-workspace-schematics", 21 | "package": "@nrwl/workspace", 22 | "name": "rename-workspace-schematics" 23 | }, 24 | { 25 | "version": "11.0.0-beta.15", 26 | "description": "Adds `outputs` based on builders", 27 | "factory": "./src/migrations/update-11-0-0/add-outputs-in-workspace", 28 | "package": "@nrwl/workspace", 29 | "name": "add-outputs-in-workspace" 30 | }, 31 | { 32 | "version": "11.0.0", 33 | "description": "Check that the right update command is used", 34 | "factory": "./src/migrations/update-11-0-0/update-command-check", 35 | "package": "@nrwl/workspace", 36 | "name": "update-command-check" 37 | }, 38 | { 39 | "version": "11.0.2", 40 | "description": "Rename the workspace-schematic script into workspace-generator script", 41 | "factory": "./src/migrations/update-11-0-0/rename-workspace-schematic-script", 42 | "package": "@nrwl/workspace", 43 | "name": "rename-workspace-schematic-script" 44 | } 45 | ] 46 | } 47 | -------------------------------------------------------------------------------- /packages/in-memory-db/src/controllers/in-memory-db.controller.ts: -------------------------------------------------------------------------------- 1 | import { Get, Post, Put, Delete, Body, Param } from '@nestjs/common'; 2 | import { InMemoryDBService } from '../services'; 3 | import { InMemoryDBEntity } from '../interfaces'; 4 | 5 | /** 6 | * @example 7 | * 8 | * ```ts 9 | * @Controller('api/user') 10 | * class UsersController extends InMemoryDBController { 11 | * 12 | * constructor(protected dbService: InMemoryDBService) { 13 | * super(dbService); 14 | * } 15 | * 16 | * } 17 | * 18 | * ``` 19 | */ 20 | export abstract class InMemoryDBEntityController { 21 | constructor(protected readonly dbService: InMemoryDBService) {} 22 | 23 | @Post() 24 | public create(@Body() record: Partial | Array>): T | T[] { 25 | if (Array.isArray(record)) { 26 | return this.dbService.createMany(record); 27 | } 28 | return this.dbService.create(record); 29 | } 30 | 31 | @Put(':id') 32 | public update(@Param('id') id: T['id'], @Body() record: Partial): void { 33 | return this.dbService.update({ id, ...record } as T); 34 | } 35 | 36 | @Put() 37 | public updateMany(@Body() records: T[]): void { 38 | return this.dbService.updateMany(records); 39 | } 40 | 41 | @Delete(':id') 42 | public delete(@Param('id') id: T['id']): void { 43 | return this.dbService.delete(id); 44 | } 45 | 46 | @Delete() 47 | public deleteMany(@Body() ids: Array): void { 48 | return this.dbService.deleteMany(ids); 49 | } 50 | 51 | @Get(':id') 52 | public get(@Param('id') id: T['id']): T { 53 | return this.dbService.get(id); 54 | } 55 | 56 | @Get() 57 | public getMany(@Body() ids?: Array): T[] { 58 | if (ids && Array.isArray(ids)) { 59 | return this.dbService.getMany(ids); 60 | } 61 | return this.dbService.getAll(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /packages/in-memory-db/src/v1/controllers/in-memory-db.controller.ts: -------------------------------------------------------------------------------- 1 | import { Get, Post, Put, Delete, Body, Param } from '@nestjs/common'; 2 | import { InMemoryDBV1Service } from '../services'; 3 | import { InMemoryDBV1Entity } from '../interfaces'; 4 | 5 | /** 6 | * @deprecated since version 2.0.0, please use InMemoryDBEntityController 7 | * @example 8 | * 9 | * ```ts 10 | * @Controller('api/user') 11 | * class UsersController extends InMemoryDBV1Controller { 12 | * 13 | * constructor(protected dbService: InMemoryDBV1Service) { 14 | * super(dbService); 15 | * } 16 | * 17 | * } 18 | * 19 | * ``` 20 | */ 21 | export abstract class InMemoryDBV1EntityController< 22 | T extends InMemoryDBV1Entity 23 | > { 24 | constructor(protected readonly dbService: InMemoryDBV1Service) {} 25 | 26 | @Post() 27 | public create(@Body() record: Partial | Array>): T | T[] { 28 | if (Array.isArray(record)) { 29 | return this.dbService.createMany(record); 30 | } 31 | return this.dbService.create(record); 32 | } 33 | 34 | @Put(':id') 35 | public update(@Param('id') id: T['id'], @Body() record: Partial): void { 36 | return this.dbService.update({ id, ...record } as T); 37 | } 38 | 39 | @Put() 40 | public updateMany(@Body() records: T[]): void { 41 | return this.dbService.updateMany(records); 42 | } 43 | 44 | @Delete(':id') 45 | public delete(@Param('id') id: T['id']): void { 46 | return this.dbService.delete(id); 47 | } 48 | 49 | @Delete() 50 | public deleteMany(@Body() ids: Array): void { 51 | return this.dbService.deleteMany(ids); 52 | } 53 | 54 | @Get(':id') 55 | public get(@Param('id') id: T['id']): T { 56 | return this.dbService.get(id); 57 | } 58 | 59 | @Get() 60 | public getMany(@Body() ids?: Array): T[] { 61 | if (ids && Array.isArray(ids)) { 62 | return this.dbService.getMany(ids); 63 | } 64 | return this.dbService.getAll(); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /packages/in-memory-db/src/controllers/in-memory-db-async.controller.ts: -------------------------------------------------------------------------------- 1 | import { Get, Post, Put, Delete, Body, Param } from '@nestjs/common'; 2 | import { InMemoryDBService } from '../services'; 3 | import { InMemoryDBEntity } from '../interfaces'; 4 | import { Observable } from 'rxjs'; 5 | 6 | /** 7 | * @example 8 | * 9 | * ```ts 10 | * @Controller('api/user') 11 | * class UsersController extends InMemoryDBEntityAsyncController { 12 | * 13 | * constructor(protected dbService: InMemoryDBService) { 14 | * super(dbService); 15 | * } 16 | * 17 | * } 18 | * 19 | * ``` 20 | */ 21 | export abstract class InMemoryDBEntityAsyncController< 22 | T extends InMemoryDBEntity 23 | > { 24 | constructor(protected readonly dbService: InMemoryDBService) {} 25 | 26 | @Post() 27 | public create( 28 | @Body() record: Partial | Array>, 29 | ): Observable { 30 | if (Array.isArray(record)) { 31 | return this.dbService.createManyAsync(record); 32 | } 33 | return this.dbService.createAsync(record); 34 | } 35 | 36 | @Put(':id') 37 | public update( 38 | @Param('id') id: T['id'], 39 | @Body() record: Partial, 40 | ): Observable { 41 | return this.dbService.updateAsync({ id, ...record } as T); 42 | } 43 | 44 | @Put() 45 | public updateMany(@Body() records: T[]): Observable { 46 | return this.dbService.updateManyAsync(records); 47 | } 48 | 49 | @Delete(':id') 50 | public delete(@Param('id') id: T['id']): Observable { 51 | return this.dbService.deleteAsync(id); 52 | } 53 | 54 | @Delete() 55 | public deleteMany(@Body() ids: Array): Observable { 56 | return this.dbService.deleteManyAsync(ids); 57 | } 58 | 59 | @Get(':id') 60 | public get(@Param('id') id: T['id']): Observable { 61 | return this.dbService.getAsync(id); 62 | } 63 | 64 | @Get() 65 | public getMany(@Body() ids?: Array): Observable { 66 | if (ids && Array.isArray(ids)) { 67 | return this.dbService.getManyAsync(ids); 68 | } 69 | return this.dbService.getAllAsync(); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /packages/in-memory-db/src/v1/controllers/in-memory-db-async.controller.ts: -------------------------------------------------------------------------------- 1 | import { Get, Post, Put, Delete, Body, Param } from '@nestjs/common'; 2 | import { InMemoryDBV1Service } from '../services'; 3 | import { InMemoryDBV1Entity } from '../interfaces'; 4 | import { Observable } from 'rxjs'; 5 | 6 | /** 7 | * @deprecated since version 2.0.0, please use InMemoryDBEntityAsyncController 8 | * @example 9 | * 10 | * ```ts 11 | * @Controller('api/user') 12 | * class UsersController extends InMemoryDBEntityV1AsyncController { 13 | * 14 | * constructor(protected dbService: InMemoryDBV1Service) { 15 | * super(dbService); 16 | * } 17 | * 18 | * } 19 | * 20 | * ``` 21 | */ 22 | export abstract class InMemoryDBV1EntityAsyncController< 23 | T extends InMemoryDBV1Entity 24 | > { 25 | constructor(protected readonly dbService: InMemoryDBV1Service) {} 26 | 27 | @Post() 28 | public create( 29 | @Body() record: Partial | Array>, 30 | ): Observable { 31 | if (Array.isArray(record)) { 32 | return this.dbService.createManyAsync(record); 33 | } 34 | return this.dbService.createAsync(record); 35 | } 36 | 37 | @Put(':id') 38 | public update( 39 | @Param('id') id: T['id'], 40 | @Body() record: Partial, 41 | ): Observable { 42 | return this.dbService.updateAsync({ id, ...record } as T); 43 | } 44 | 45 | @Put() 46 | public updateMany(@Body() records: T[]): Observable { 47 | return this.dbService.updateManyAsync(records); 48 | } 49 | 50 | @Delete(':id') 51 | public delete(@Param('id') id: T['id']): Observable { 52 | return this.dbService.deleteAsync(id); 53 | } 54 | 55 | @Delete() 56 | public deleteMany(@Body() ids: Array): Observable { 57 | return this.dbService.deleteManyAsync(ids); 58 | } 59 | 60 | @Get(':id') 61 | public get(@Param('id') id: T['id']): Observable { 62 | return this.dbService.getAsync(id); 63 | } 64 | 65 | @Get() 66 | public getMany(@Body() ids?: Array): Observable { 67 | if (ids && Array.isArray(ids)) { 68 | return this.dbService.getManyAsync(ids); 69 | } 70 | return this.dbService.getAllAsync(); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /packages/docs/docusaurus.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | title: 'Nestjs-Addons', 3 | tagline: '', 4 | url: 'https://your-docusaurus-test-site.com', 5 | baseUrl: '/', 6 | onBrokenLinks: 'throw', 7 | favicon: 'img/favicon.ico', 8 | organizationName: '@nestjs-addons', 9 | projectName: '@nestjs-addons', 10 | themeConfig: { 11 | navbar: { 12 | title: 'NestJS Addons', 13 | logo: { 14 | alt: 'NestJS Addons Logo', 15 | src: 'img/logo.png', 16 | }, 17 | items: [ 18 | { 19 | to: 'docs/docs', 20 | activeBasePath: 'docs', 21 | label: 'Docs', 22 | position: 'left', 23 | }, 24 | //{ to: 'blog', label: 'Blog', position: 'left' }, 25 | { 26 | href: 'https://github.com/nestjs-addons/platform', 27 | label: 'GitHub', 28 | position: 'right', 29 | }, 30 | ], 31 | }, 32 | footer: { 33 | style: 'dark', 34 | links: [ 35 | { 36 | title: 'Docs', 37 | items: [ 38 | { 39 | label: 'Style Guide', 40 | to: 'docs/docs', 41 | }, 42 | ], 43 | }, 44 | { 45 | title: 'More', 46 | items: [ 47 | /*{ 48 | label: 'Blog', 49 | to: 'blog', 50 | },*/ 51 | { 52 | label: 'GitHub', 53 | href: 'https://github.com/nestjs-addons/platform', 54 | }, 55 | ], 56 | }, 57 | ], 58 | copyright: `Copyright © ${new Date().getFullYear()} NestJS Addons. Built with Docusaurus.`, 59 | }, 60 | }, 61 | presets: [ 62 | [ 63 | '@docusaurus/preset-classic', 64 | { 65 | docs: { 66 | sidebarPath: require.resolve('./sidebars.js'), 67 | editUrl: 68 | 'https://github.com/nestjs-addons/platform/edit/master/packages/docs/docs/', 69 | }, 70 | blog: { 71 | showReadingTime: true, 72 | editUrl: 73 | 'https://github.com/nestjs-addons/platform/edit/master/packages/docs/blog/', 74 | }, 75 | theme: { 76 | customCss: require.resolve('./src/css/custom.css'), 77 | }, 78 | }, 79 | ], 80 | [ 81 | '@docusaurus/plugin-sitemap', 82 | { 83 | cacheTime: 600 * 1000, // 600 sec - cache purge period 84 | changefreq: 'weekly', 85 | priority: 0.5, 86 | trailingSlash: false, 87 | }, 88 | ], 89 | ], 90 | }; 91 | -------------------------------------------------------------------------------- /packages/docs/docs/IMDB-quickstart.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: in-memory-db-quickstart 3 | title: Quick Start 4 | --- 5 | 6 | ## Import into Module(s) 7 | 8 | To get started, let's first update our `app.module.ts` to include the necessary pieces. 9 | 10 | > While we are importing to the AppModule in this example, InMemoryDBModule could be imported in Feature modules just as well. 11 | 12 | ### Registering a forRoot InMemoryDBModule 13 | 14 | ```typescript 15 | // app.module.ts 16 | 17 | import { Module } from '@nestjs/common'; 18 | import { InMemoryDBModule } from '@nestjs-addons/in-memory-db'; 19 | ... 20 | 21 | @Module({ 22 | ... 23 | imports: [InMemoryDBModule.forRoot({})], 24 | ... 25 | }) 26 | export class AppModule {} 27 | ``` 28 | 29 | As you can see we did the following: 30 | 31 | - Import `InMemoryDBModule` from `@nestjs-addons/in-memory-db` 32 | - Add `InMemoryDBModule` to the `imports` array in the `@Module` of your choice 33 | 34 | ## Define an interface for each InMemoryEntity 35 | 36 | An instance of `InMemoryDBService` will be created for each `InMemoryDBEntity` entity `interface` defined. The `InMemoryDBEntity` adds an `id: string` property as the only required field. Additional fields can be defined by extending the `interface`. 37 | 38 | To define a new `InMemoryDBEntity` extension create an `interface` similar to the following example: 39 | 40 | ```typescript 41 | interface UserEntity extends InMemoryDBEntity { 42 | firstName: string; 43 | lastName: string; 44 | emailAddress: string; 45 | admin: boolean; 46 | } 47 | ``` 48 | 49 | Now we can make use of our new `interface` when injecting the `InMemoryDBService` into our controllers or other services. 50 | 51 | ## Inject into Controller(s) and/or Services(s) 52 | 53 | In order to use the `InMemoryDBService` we need to do the following: 54 | 55 | - Add `private readonly inMemoryDB: InMemoryDBService` to the `constructor` of each controller and/or service that you would like to use it in. 56 | - Begin using `InMemoryDBService` as expected. 57 | 58 | An example of injecting `InMemoryDBService` into a `UserController` for the `UserEntity` we defined earlier would look something like this: 59 | 60 | ```typescript 61 | @Controller() 62 | export class UserController { 63 | constructor(private readonly userService: InMemoryDBService) {} 64 | 65 | @Get('users/:id') 66 | getUser(@Param() id: string): UserEntity { 67 | return this.userService.get(id); 68 | } 69 | 70 | @Post('users') 71 | createUser(@Body() user: UserEntity): UserEntity { 72 | return this.userService.create(user); 73 | } 74 | } 75 | ``` 76 | -------------------------------------------------------------------------------- /packages/playground-e2e/src/app-product.e2e.spec.ts: -------------------------------------------------------------------------------- 1 | import { INestApplication } from '@nestjs/common'; 2 | import { TestingModule, Test } from '@nestjs/testing'; 3 | import { ProductModule } from '../../playground/src/product/product.module'; 4 | import * as request from 'supertest'; 5 | import { Product } from '../../playground/src/product/product'; 6 | 7 | describe('ProductController (e2e)', () => { 8 | let app: INestApplication; 9 | 10 | beforeAll(async () => { 11 | const fixture: TestingModule = await Test.createTestingModule({ 12 | imports: [ProductModule], 13 | }).compile(); 14 | 15 | app = fixture.createNestApplication(); 16 | await app.init(); 17 | }); 18 | 19 | describe('Verifies their are no records', () => { 20 | test('/api/products (GET) - Get All Products = []', () => { 21 | return request(app.getHttpServer()) 22 | .get('/api/products') 23 | .expect(200) 24 | .expect([]); 25 | }); 26 | }); 27 | 28 | describe('Create, Update, Read & Delete Products', () => { 29 | const product: Product = { 30 | id: '1', 31 | name: 'Cheeseburger', 32 | cost: 3.99, 33 | units: 10000, 34 | }; 35 | 36 | test('/api/products (POST) - Create Product 1', () => { 37 | return request(app.getHttpServer()) 38 | .post('/api/products') 39 | .send(product) 40 | .expect(201) 41 | .expect(product); 42 | }); 43 | 44 | test('/api/products/1 (PUT) - Update Product 1', () => { 45 | product.cost = 3.5; 46 | return request(app.getHttpServer()) 47 | .put('/api/products/1') 48 | .send(product) 49 | .expect(200) 50 | .expect({}); 51 | }); 52 | 53 | test('/api/products/1 (GET) - Get Product 1', () => { 54 | return request(app.getHttpServer()) 55 | .get('/api/products/1') 56 | .expect(200) 57 | .expect(product); 58 | }); 59 | 60 | test('/api/products/ (GET) - Get All Products', () => { 61 | return request(app.getHttpServer()) 62 | .get('/api/products') 63 | .expect(200) 64 | .expect([product]); 65 | }); 66 | 67 | test('/api/products/1 (DELETE) - Delete Product 1', () => { 68 | return request(app.getHttpServer()) 69 | .delete('/api/products/1') 70 | .expect(200) 71 | .expect({}); 72 | }); 73 | 74 | test('/api/products (GET) - Get All Products = []', () => { 75 | return request(app.getHttpServer()) 76 | .get('/api/products') 77 | .expect(200) 78 | .expect([]); 79 | }); 80 | }); 81 | 82 | afterAll(async () => { 83 | await app.close(); 84 | }); 85 | }); 86 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nestjs-addons", 3 | "version": "0.0.0", 4 | "license": "MIT", 5 | "scripts": { 6 | "nx": "nx", 7 | "start": "nx serve", 8 | "build": "nx build", 9 | "test": "nx test", 10 | "lint": "nx workspace-lint && nx lint", 11 | "e2e": "nx e2e", 12 | "affected:apps": "nx affected:apps", 13 | "affected:libs": "nx affected:libs", 14 | "affected:build": "nx affected:build", 15 | "affected:e2e": "nx affected:e2e", 16 | "affected:test": "nx affected:test", 17 | "affected:lint": "nx affected:lint", 18 | "affected:dep-graph": "nx affected:dep-graph", 19 | "affected": "nx affected", 20 | "format": "nx format:write", 21 | "format:write": "nx format:write", 22 | "format:check": "nx format:check", 23 | "update": "nx migrate latest", 24 | "dep-graph": "nx dep-graph", 25 | "help": "nx help", 26 | "commit": "git-cz", 27 | "workspace-generator": "nx workspace-generator", 28 | "publish:in-memory-db": "npm publish dist/packages/in-memory-db" 29 | }, 30 | "private": true, 31 | "dependencies": { 32 | "@docusaurus/core": "^2.0.0-alpha.70", 33 | "@docusaurus/plugin-sitemap": "^2.0.0-alpha.70", 34 | "@docusaurus/preset-classic": "^2.0.0-alpha.70", 35 | "@nestjs/common": "^7.6.11", 36 | "@nestjs/core": "^7.6.11", 37 | "@nestjs/platform-express": "^7.6.11", 38 | "clsx": "^1.1.1", 39 | "react": "^16.8.4", 40 | "react-dom": "^16.8.4", 41 | "reflect-metadata": "^0.1.13", 42 | "rxjs": "~6.6.3" 43 | }, 44 | "devDependencies": { 45 | "@nestjs/cli": "^7.5.4", 46 | "@nestjs/schematics": "^7.2.7", 47 | "@nestjs/testing": "^7.6.11", 48 | "@nrwl/cli": "11.2.12", 49 | "@nrwl/eslint-plugin-nx": "11.2.12", 50 | "@nrwl/jest": "11.2.12", 51 | "@nrwl/nest": "11.2.12", 52 | "@nrwl/node": "11.2.12", 53 | "@nrwl/tao": "11.2.12", 54 | "@nx-plus/docusaurus": "10.4.0", 55 | "@nrwl/workspace": "11.2.12", 56 | "@types/jest": "26.0.15", 57 | "@types/node": "12.12.38", 58 | "@typescript-eslint/eslint-plugin": "4.3.0", 59 | "@typescript-eslint/parser": "4.3.0", 60 | "cz-conventional-changelog": "^3.3.0", 61 | "dotenv": "6.2.0", 62 | "eslint": "7.24.0", 63 | "eslint-config-prettier": "6.15.0", 64 | "jest": "26.6.3", 65 | "prettier": "2.2.1", 66 | "rxjs-marbles": "^6.0.1", 67 | "supertest": "^4.0.2", 68 | "ts-jest": "26.4.0", 69 | "ts-node": "9.1.1", 70 | "tslint": "6.1.3", 71 | "typescript": "4.0.5" 72 | }, 73 | "resolutions": { 74 | "terser": "^4.0.0" 75 | }, 76 | "config": { 77 | "commitizen": { 78 | "path": "./node_modules/cz-conventional-changelog" 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /packages/playground/src/customer/customer.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { CustomerController } from './customer.controller'; 3 | import { InMemoryDBModule } from '@nestjs-addons/in-memory-db'; 4 | import { Customer } from './customer'; 5 | 6 | describe('Customer Controller', () => { 7 | let controller: CustomerController; 8 | 9 | beforeEach(async () => { 10 | const module: TestingModule = await Test.createTestingModule({ 11 | controllers: [CustomerController], 12 | imports: [InMemoryDBModule.forFeature('customer')], 13 | }).compile(); 14 | 15 | controller = module.get(CustomerController); 16 | }); 17 | 18 | test('should be defined', () => { 19 | expect(controller).toBeTruthy(); 20 | }); 21 | 22 | describe('CRUD Operations for ', () => { 23 | const customer: Customer = { 24 | id: '1', 25 | firstName: 'Kamil', 26 | lastName: 'Myśliwiec', 27 | company: 'NestJs', 28 | title: 'Owner', 29 | }; 30 | 31 | test('create', () => { 32 | // arrange 33 | const expectedResult = customer; 34 | 35 | // act 36 | jest.spyOn(controller, 'create').mockImplementation(() => expectedResult); 37 | 38 | // assert 39 | expect(controller.create(customer)).toBe(expectedResult); 40 | }); 41 | 42 | test('getAll', () => { 43 | // arrange 44 | const expectedResult = [customer]; 45 | 46 | // act 47 | jest 48 | .spyOn(controller, 'getMany') 49 | .mockImplementation(() => expectedResult); 50 | 51 | // assert 52 | expect(controller.getMany()).toBe(expectedResult); 53 | }); 54 | 55 | test('get', () => { 56 | // arrange 57 | const expectResult = customer; 58 | 59 | // act 60 | jest.spyOn(controller, 'get').mockImplementation(() => expectResult); 61 | 62 | // assert 63 | expect(controller.get('1')).toBe(expectResult); 64 | }); 65 | 66 | test('update', () => { 67 | // arrange 68 | customer.company = 'NestJS'; 69 | const expectedResult = customer; 70 | 71 | // act 72 | jest.spyOn(controller, 'update').mockImplementation(() => expectedResult); 73 | 74 | // assert 75 | expect(controller.update(customer.id, { ...customer })).toBe( 76 | expectedResult, 77 | ); 78 | }); 79 | 80 | test('delete', () => { 81 | // arrange 82 | const expectedResult = null; 83 | 84 | // act 85 | jest.spyOn(controller, 'delete').mockImplementation(() => expectedResult); 86 | 87 | // assert 88 | expect(controller.delete('1')).toBe(expectedResult); 89 | }); 90 | }); 91 | }); 92 | -------------------------------------------------------------------------------- /packages/playground-e2e/src/app-customer.e2e.spec.ts: -------------------------------------------------------------------------------- 1 | import { INestApplication } from '@nestjs/common'; 2 | import { TestingModule, Test } from '@nestjs/testing'; 3 | import { CustomerModule } from '../../playground/src/customer/customer.module'; 4 | import * as request from 'supertest'; 5 | import { Customer } from '../../playground/src/customer/customer'; 6 | 7 | describe('CustomerController (e2e)', () => { 8 | let app: INestApplication; 9 | 10 | beforeAll(async () => { 11 | const fixture: TestingModule = await Test.createTestingModule({ 12 | imports: [CustomerModule], 13 | }).compile(); 14 | 15 | app = fixture.createNestApplication(); 16 | await app.init(); 17 | }); 18 | 19 | describe('Verifies their are no records', () => { 20 | test('/api/customers (GET) - Get All Customers = []', () => { 21 | return request(app.getHttpServer()) 22 | .get('/api/customers') 23 | .expect(200) 24 | .expect([]); 25 | }); 26 | }); 27 | 28 | describe('Create, Update, Read & Delete Customer', () => { 29 | const customer: Customer = { 30 | id: '1', 31 | firstName: 'Kamil', 32 | lastName: 'Myśliwiec', 33 | company: 'NestJS', 34 | title: 'Founder', 35 | }; 36 | 37 | test('/api/customers (POST) - Create Customer 1', () => { 38 | return request(app.getHttpServer()) 39 | .post('/api/customers') 40 | .send(customer) 41 | .expect(201) 42 | .expect(customer); 43 | }); 44 | 45 | test('/api/customers/1 (PUT) - Update Customer 1', () => { 46 | customer.title = 'CEO & Founder'; 47 | return request(app.getHttpServer()) 48 | .put('/api/customers/1') 49 | .send(customer) 50 | .expect(200) 51 | .expect({}); 52 | }); 53 | 54 | test('/api/customers/1 (GET) - Get Customer 1', () => { 55 | return request(app.getHttpServer()) 56 | .get('/api/customers/1') 57 | .expect(200) 58 | .expect(customer); 59 | }); 60 | 61 | test('/api/customers (GET) - Get All Customers', () => { 62 | return request(app.getHttpServer()) 63 | .get('/api/customers') 64 | .expect(200) 65 | .expect([customer]); 66 | }); 67 | 68 | test('/api/customers/1 (DELETE) - Delete Customer 1', () => { 69 | return request(app.getHttpServer()) 70 | .delete('/api/customers/1') 71 | .expect(200) 72 | .expect({}); 73 | }); 74 | 75 | test('/api/customers (GET) - Get All Customers = []', () => { 76 | return request(app.getHttpServer()) 77 | .get('/api/customers') 78 | .expect(200) 79 | .expect([]); 80 | }); 81 | }); 82 | 83 | afterAll(async () => { 84 | await app.close(); 85 | }); 86 | }); 87 | -------------------------------------------------------------------------------- /packages/playground/src/product/product.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { ProductController } from './product.controller'; 3 | import { InMemoryDBModule } from '@nestjs-addons/in-memory-db'; 4 | import { Product } from './product'; 5 | 6 | describe('Product Controller', () => { 7 | let controller: ProductController; 8 | 9 | beforeEach(async () => { 10 | const module: TestingModule = await Test.createTestingModule({ 11 | controllers: [ProductController], 12 | imports: [InMemoryDBModule.forFeature('product')], 13 | }).compile(); 14 | 15 | controller = module.get(ProductController); 16 | }); 17 | 18 | it('should be defined', () => { 19 | expect(controller).toBeTruthy(); 20 | }); 21 | 22 | describe('CRUD Operations for Products', () => { 23 | const product: Product = { 24 | id: '1', 25 | name: 'Cheeseburger', 26 | cost: 3.99, 27 | units: 10000, 28 | }; 29 | 30 | test('createProduct', () => { 31 | // arrange 32 | const expectedResult = product; 33 | 34 | // act 35 | jest 36 | .spyOn(controller, 'createProduct') 37 | .mockImplementation(() => expectedResult); 38 | 39 | // assert 40 | expect(controller.createProduct(product)).toBe(expectedResult); 41 | }); 42 | 43 | test('getCustomer', () => { 44 | // arrange 45 | const expectedResult = product; 46 | 47 | // act 48 | jest 49 | .spyOn(controller, 'getProduct') 50 | .mockImplementation(() => expectedResult); 51 | 52 | // assert 53 | expect(controller.getProduct('1')).toBe(expectedResult); 54 | }); 55 | 56 | test('updateProduct', () => { 57 | // arrange 58 | product.cost = 4.25; 59 | const expectedResult = product; 60 | 61 | // act 62 | jest 63 | .spyOn(controller, 'updateProduct') 64 | .mockImplementation(() => expectedResult); 65 | 66 | // assert 67 | expect(controller.updateProduct(product)).toBe(expectedResult); 68 | }); 69 | 70 | test('deleteProduct', () => { 71 | // arrange 72 | const expectedResult = null; 73 | 74 | // act 75 | jest 76 | .spyOn(controller, 'deleteProduct') 77 | .mockImplementation(() => expectedResult); 78 | 79 | // assert 80 | expect(controller.deleteProduct('1')).toBe(expectedResult); 81 | }); 82 | 83 | test('getProducts', () => { 84 | // arrange 85 | const expectedResult = []; 86 | 87 | // act 88 | jest 89 | .spyOn(controller, 'getProducts') 90 | .mockImplementation(() => expectedResult); 91 | 92 | // assert 93 | expect(controller.getProducts()).toBe(expectedResult); 94 | }); 95 | }); 96 | }); 97 | -------------------------------------------------------------------------------- /packages/in-memory-db/src/schematics/nest-add/index.ts: -------------------------------------------------------------------------------- 1 | import { JsonParseMode, parseJson, Path } from '@angular-devkit/core'; 2 | import { 3 | branchAndMerge, 4 | chain, 5 | Rule, 6 | SchematicsException, 7 | Tree, 8 | } from '@angular-devkit/schematics'; 9 | import * as ts from 'typescript'; 10 | import { addImportToModule, insertImport } from '../utils/ast-utils'; 11 | import { InsertChange } from '../utils/change'; 12 | import { ModuleFinder } from '../utils/module.finder'; 13 | import { NestAddOptions } from './schema'; 14 | 15 | function getWorkspace(tree: Tree): { path: string; workspace: any } { 16 | const possibleFiles = ['/nest-cli.json']; 17 | const path = possibleFiles.filter((configPath) => tree.exists(configPath))[0]; 18 | 19 | const configBuffer = tree.read(path); 20 | if (configBuffer === null) { 21 | throw new SchematicsException(`Could not find nest-cli.json`); 22 | } 23 | const content = configBuffer.toString(); 24 | let workspace: any; 25 | try { 26 | workspace = parseJson(content, JsonParseMode.Loose); 27 | } catch (e) { 28 | throw new SchematicsException( 29 | `Could not parse nest-cli.json: ` + e.message, 30 | ); 31 | } 32 | return { 33 | path, 34 | workspace, 35 | }; 36 | } 37 | 38 | function addDeclarationToModule(options: NestAddOptions): Rule { 39 | return (tree: Tree) => { 40 | if (options.skipImport !== undefined && options.skipImport) { 41 | return tree; 42 | } 43 | 44 | const { workspace } = getWorkspace(tree); 45 | 46 | options.module = new ModuleFinder(tree).find({ 47 | name: options.module ? options.module : 'app', 48 | path: ('./' + workspace.sourceRoot) as Path, 49 | }); 50 | 51 | if (!tree.exists(options.module)) { 52 | throw new SchematicsException( 53 | `Could not find root module, please use --module flag to specify the root module path.`, 54 | ); 55 | } 56 | 57 | const content = tree.read(options.module).toString(); 58 | const modulePath = 59 | './' + workspace.sourceRoot + '/' + options.module 60 | ? options.module 61 | : 'app'; 62 | 63 | const source = ts.createSourceFile( 64 | modulePath, 65 | content, 66 | ts.ScriptTarget.Latest, 67 | true, 68 | ); 69 | 70 | const importChanges = addImportToModule( 71 | source, 72 | modulePath, 73 | 'InMemoryDBModule.forRoot()', 74 | '@nestjs-addons/in-memory-db', 75 | ).shift(); 76 | 77 | const commonImports = [ 78 | insertImport( 79 | source, 80 | modulePath, 81 | 'InMemoryDBModule', 82 | '@nestjs-addons/in-memory-db', 83 | ), 84 | importChanges, 85 | ]; 86 | const changes = [...commonImports]; 87 | const recorder = tree.beginUpdate(modulePath); 88 | for (const change of changes) { 89 | if (change instanceof InsertChange) { 90 | recorder.insertLeft(change.pos, change.toAdd); 91 | } 92 | } 93 | tree.commitUpdate(recorder); 94 | 95 | return tree; 96 | }; 97 | } 98 | 99 | export default function (options: NestAddOptions): Rule { 100 | return branchAndMerge(chain([addDeclarationToModule(options)])); 101 | } 102 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at wesgrimes@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /packages/spectator/src/lib/mock.ts: -------------------------------------------------------------------------------- 1 | /** Credit to: Valentin Buryakov 2 | * @ngneat/spectator - https://github.com/ngneat/spectator 3 | */ 4 | import { Provider, Type } from '@nestjs/common'; 5 | import { FactoryProvider } from '@nestjs/common/interfaces'; 6 | 7 | type Writable = { -readonly [P in keyof T]: T[P] }; 8 | 9 | /** 10 | * @publicApi 11 | */ 12 | export type BaseSpyObject = T & 13 | { 14 | [P in keyof T]: T[P] extends (...args: any[]) => any 15 | ? T[P] & CompatibleSpy 16 | : T[P]; 17 | } & { 18 | /** 19 | * Casts to type without readonly properties 20 | */ 21 | castToWritable(): Writable; 22 | }; 23 | 24 | /** 25 | * @publicApi 26 | */ 27 | export interface CompatibleSpy extends jasmine.Spy { 28 | /** 29 | * By chaining the spy with and.returnValue, all calls to the function will return a specific 30 | * value. 31 | */ 32 | andReturn(val: any): void; 33 | 34 | /** 35 | * By chaining the spy with and.callFake, all calls to the spy will delegate to the supplied 36 | * function. 37 | */ 38 | andCallFake(fn: (...args: any[]) => any): this; 39 | 40 | /** 41 | * removes all recorded calls 42 | */ 43 | reset(): void; 44 | } 45 | 46 | /** 47 | * @publicApi 48 | */ 49 | export type SpyObject = BaseSpyObject & 50 | { 51 | [P in keyof T]: T[P] & 52 | (T[P] extends (...args: any[]) => infer R ? jest.Mock : T[P]); 53 | }; 54 | 55 | /** 56 | * @internal 57 | */ 58 | export function installProtoMethods( 59 | mock: any, 60 | proto: any, 61 | createSpyFn: (...args: any[]) => any, 62 | ): void { 63 | if (proto === null || proto === Object.prototype) { 64 | return; 65 | } 66 | 67 | for (const key of Object.getOwnPropertyNames(proto)) { 68 | const descriptor = Object.getOwnPropertyDescriptor(proto, key); 69 | 70 | if (!descriptor) { 71 | continue; 72 | } 73 | 74 | if ( 75 | typeof descriptor.value === 'function' && 76 | key !== 'constructor' && 77 | typeof mock[key] === 'undefined' 78 | ) { 79 | mock[key] = createSpyFn(key); 80 | } else if ( 81 | descriptor.get && 82 | !Object.prototype.hasOwnProperty.call(mock, key) 83 | ) { 84 | Object.defineProperty(mock, key, { 85 | set: (value) => (mock[`_${key}`] = value), 86 | get: () => mock[`_${key}`], 87 | }); 88 | } 89 | } 90 | 91 | installProtoMethods(mock, Object.getPrototypeOf(proto), createSpyFn); 92 | 93 | mock.castToWritable = () => mock; 94 | } 95 | 96 | /** 97 | * @publicApi 98 | */ 99 | export function createSpyObject( 100 | type: Provider & { prototype: T }, 101 | template?: Partial>, 102 | ): SpyObject { 103 | const mock: any = { ...template } || {}; 104 | 105 | installProtoMethods(mock, type.prototype, () => { 106 | const jestFn = jest.fn(); 107 | const newSpy: CompatibleSpy = jestFn as any; 108 | 109 | newSpy.andCallFake = (fn: (...args: any[]) => any) => { 110 | jestFn.mockImplementation(fn as (...args: any[]) => any); 111 | 112 | return newSpy; 113 | }; 114 | 115 | newSpy.andReturn = (val: any) => { 116 | jestFn.mockReturnValue(val); 117 | }; 118 | 119 | newSpy.reset = () => { 120 | jestFn.mockReset(); 121 | }; 122 | 123 | return newSpy; 124 | }); 125 | 126 | return mock; 127 | } 128 | 129 | /** 130 | * @publicApi 131 | */ 132 | export function mockProvider( 133 | type: Type, 134 | properties?: Partial>, 135 | ): FactoryProvider { 136 | return { 137 | provide: type, 138 | useFactory: () => createSpyObject(type, properties), 139 | }; 140 | } 141 | -------------------------------------------------------------------------------- /packages/playground/src/app.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Body, 3 | Controller, 4 | Delete, 5 | Get, 6 | Param, 7 | Post, 8 | Put, 9 | } from '@nestjs/common'; 10 | import { InMemoryDBService } from '@nestjs-addons/in-memory-db'; 11 | 12 | import { Observable } from 'rxjs'; 13 | import { User } from './user'; 14 | 15 | @Controller('api/') 16 | export class AppController { 17 | constructor(private readonly inMemoryDBService: InMemoryDBService) {} 18 | 19 | @Get('user/:id') 20 | getUser(@Param('id') id: string): User { 21 | return this.inMemoryDBService.get(id); 22 | } 23 | 24 | @Get('user/:id/async') 25 | getUserAsync(@Param('id') id: string): Observable { 26 | return this.inMemoryDBService.getAsync(id); 27 | } 28 | 29 | @Get('users') 30 | getUsers(): User[] { 31 | return this.inMemoryDBService.getAll(); 32 | } 33 | 34 | @Get('users/async') 35 | getUsersAsync(): Observable { 36 | return this.inMemoryDBService.getAllAsync(); 37 | } 38 | 39 | @Get('users/firstName/:firstName') 40 | getByFirstName(@Param('firstName') firstName: string): User[] { 41 | return this.inMemoryDBService.query((user) => user.firstName === firstName); 42 | } 43 | 44 | @Get('users/firstName/:firstName/async') 45 | getByFirstNameAsync( 46 | @Param('firstName') firstName: string, 47 | ): Observable { 48 | return this.inMemoryDBService.queryAsync( 49 | (user) => user.firstName === firstName, 50 | ); 51 | } 52 | 53 | @Get('users/lastname/:lastName') 54 | getByLastName(@Param('lastName') lastName: string): User[] { 55 | return this.inMemoryDBService.query((user) => user.lastName === lastName); 56 | } 57 | 58 | @Get('users/lastName/:lastName/async') 59 | getByLastNameAsync(@Param('lastName') lastName: string): Observable { 60 | return this.inMemoryDBService.queryAsync( 61 | (user) => user.lastName === lastName, 62 | ); 63 | } 64 | 65 | @Post('user') 66 | createUser(@Body() user: User): User { 67 | return this.inMemoryDBService.create(user); 68 | } 69 | 70 | @Post('user/async') 71 | createUserAsync(@Body() user: User): Observable { 72 | return this.inMemoryDBService.createAsync(user); 73 | } 74 | 75 | @Post('users') 76 | createUsers(@Body() users: User[]): User[] { 77 | return this.inMemoryDBService.createMany(users); 78 | } 79 | 80 | @Post('users/async') 81 | createUsersAsync(@Body() users: User[]): Observable { 82 | return this.inMemoryDBService.createManyAsync(users); 83 | } 84 | 85 | @Put('user/:id') 86 | updateUser(@Body() user: User): void { 87 | return this.inMemoryDBService.update(user); 88 | } 89 | 90 | @Put('user/:id/async') 91 | updateUserAsync(@Body() user: User): Observable { 92 | return this.inMemoryDBService.updateAsync(user); 93 | } 94 | 95 | @Put('users') 96 | updateUsers(@Body() users: User[]): void { 97 | return this.inMemoryDBService.updateMany(users); 98 | } 99 | 100 | @Put('users/async') 101 | updateUsersAsync(@Body() users: User[]): Observable { 102 | return this.inMemoryDBService.updateManyAsync(users); 103 | } 104 | 105 | @Delete('user/:id') 106 | deleteUser(@Param('id') id: string): void { 107 | return this.inMemoryDBService.delete(id); 108 | } 109 | 110 | @Delete('user/:id/async') 111 | deleteUserAsync(@Param('id') id: string): Observable { 112 | return this.inMemoryDBService.deleteAsync(id); 113 | } 114 | 115 | @Delete('users') 116 | deleteUsers(@Body() ids: string[]): void { 117 | return this.inMemoryDBService.deleteMany(ids); 118 | } 119 | 120 | @Delete('users/async') 121 | deleteUsersAsync(@Body() ids: string[]): Observable { 122 | return this.inMemoryDBService.deleteManyAsync(ids); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Stay in touch 3 | 4 | - Author - [Wes Grimes](https://wesleygrimes.com) 5 | - Website - [https://github.com/nestjs-addons/platform](https://github.com/nestjs-addons/platform/) 6 | - Twitter - [@wesgrimes](https://twitter.com/wesgrimes) 7 | 8 | ## Maintainers ✨ 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
Wes Grimes
Wes Grimes

🚇 ⚠️ 💻
Jay Bell
Jay Bell

⚠️ 💻
Dominik Pieper
Dominik Pieper

⚠️ 💻
19 | 20 | 21 | 22 | ## Contributors ✨ 23 | 24 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 |
Chris Whited
Chris Whited

🚇 ⚠️ 💻
Wes Copeland
Wes Copeland

💻 ⚠️
Jordan
Jordan

💻 ⚠️
Santosh Yadav
Santosh Yadav

💻 ⚠️
Itay Oded
Itay Oded

💻 ⚠️
37 | 38 | 39 | 40 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! 41 | 42 | ## License 43 | 44 | NestJS Addons is [MIT licensed](LICENSE). 45 | -------------------------------------------------------------------------------- /packages/playground-e2e/src/app-customer-product.e2e.spec.ts: -------------------------------------------------------------------------------- 1 | import { INestApplication } from '@nestjs/common'; 2 | import { TestingModule, Test } from '@nestjs/testing'; 3 | import { CustomerProductModule } from '../../playground/src/customer-product/customer-product.module'; 4 | import * as request from 'supertest'; 5 | import { Product } from '../../playground/src/product/product'; 6 | import { Customer } from '../../playground/src/customer/customer'; 7 | 8 | describe('ProductController & CustomerController (e2e)', () => { 9 | let app: INestApplication; 10 | 11 | beforeAll(async () => { 12 | const fixture: TestingModule = await Test.createTestingModule({ 13 | imports: [CustomerProductModule], 14 | }).compile(); 15 | 16 | app = fixture.createNestApplication(); 17 | await app.init(); 18 | }); 19 | 20 | describe('Verifies their are no records', () => { 21 | test('/api/products (GET) - Get All Products = []', () => { 22 | return request(app.getHttpServer()) 23 | .get('/api/products') 24 | .expect(200) 25 | .expect([]); 26 | }); 27 | 28 | test('/api/customers (GET) - Get All Customers = []', () => { 29 | return request(app.getHttpServer()) 30 | .get('/api/customers') 31 | .expect(200) 32 | .expect([]); 33 | }); 34 | }); 35 | 36 | describe('Create, Update, Read & Delete Customers & Products', () => { 37 | const product: Product = { 38 | id: '1', 39 | name: 'Cheeseburger', 40 | cost: 3.99, 41 | units: 10000, 42 | }; 43 | const customer: Customer = { 44 | id: '1', 45 | firstName: 'Kamil', 46 | lastName: 'Myśliwiec', 47 | company: 'NestJS', 48 | title: 'Founder', 49 | }; 50 | 51 | describe('Create', () => { 52 | test('/api/products (POST) - Create Product 1', () => { 53 | return request(app.getHttpServer()) 54 | .post('/api/products') 55 | .send(product) 56 | .expect(201) 57 | .expect(product); 58 | }); 59 | 60 | test('/api/customers (POST) - Create Customer 1', () => { 61 | return request(app.getHttpServer()) 62 | .post('/api/customers') 63 | .send(customer) 64 | .expect(201) 65 | .expect(customer); 66 | }); 67 | }); 68 | 69 | describe('Update', () => { 70 | test('/api/products/1 (PUT) - Update Product 1', () => { 71 | product.cost = 3.5; 72 | return request(app.getHttpServer()) 73 | .put('/api/products/1') 74 | .send(product) 75 | .expect(200) 76 | .expect({}); 77 | }); 78 | 79 | test('/api/customers/1 (PUT) - Update Customer 1', () => { 80 | customer.title = 'CEO & Founder'; 81 | return request(app.getHttpServer()) 82 | .put('/api/customers/1') 83 | .send(customer) 84 | .expect(200) 85 | .expect({}); 86 | }); 87 | }); 88 | 89 | describe('Get', () => { 90 | test('/api/products/1 (GET) - Get Product 1', () => { 91 | return request(app.getHttpServer()) 92 | .get('/api/products/1') 93 | .expect(200) 94 | .expect(product); 95 | }); 96 | 97 | test('/api/customers/1 (GET) - Get Customer 1', () => { 98 | return request(app.getHttpServer()) 99 | .get('/api/customers/1') 100 | .expect(200) 101 | .expect(customer); 102 | }); 103 | 104 | test('/api/products (GET) - Get All Products', () => { 105 | return request(app.getHttpServer()) 106 | .get('/api/products') 107 | .expect(200) 108 | .expect([product]); 109 | }); 110 | 111 | test('/api/customers (GET) - Get All Customers', () => { 112 | return request(app.getHttpServer()) 113 | .get('/api/customers') 114 | .expect(200) 115 | .expect([customer]); 116 | }); 117 | }); 118 | 119 | describe('Delete', () => { 120 | test('/api/products/1 (DELETE) - Delete Products 1', () => { 121 | return request(app.getHttpServer()) 122 | .delete('/api/products/1') 123 | .expect(200) 124 | .expect({}); 125 | }); 126 | 127 | test('/api/customers/1 (DELETE) - Delete Customer 1', () => { 128 | return request(app.getHttpServer()) 129 | .delete('/api/customers/1') 130 | .expect(200) 131 | .expect({}); 132 | }); 133 | }); 134 | }); 135 | 136 | afterAll(async () => { 137 | await app.close(); 138 | }); 139 | }); 140 | -------------------------------------------------------------------------------- /packages/in-memory-db/src/v1/controllers/in-memory-db.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { InMemoryDBV1Entity } from '../interfaces'; 2 | import { inMemoryDBV1ServiceFactory } from '../factories'; 3 | import { InMemoryDBV1EntityController } from './in-memory-db.controller'; 4 | import { InMemoryDBV1Service } from '../services'; 5 | 6 | describe('In Memory DB Controller', () => { 7 | interface TestEntity extends InMemoryDBV1Entity { 8 | someField: string; 9 | } 10 | 11 | let controller: InMemoryDBV1EntityController; 12 | let service: InMemoryDBV1Service; 13 | 14 | const sampleRecords: TestEntity[] = [ 15 | { id: 1, someField: 'AAA' }, 16 | { id: 2, someField: 'BBB' }, 17 | { id: 3, someField: 'CCC' }, 18 | ]; 19 | 20 | class MockController extends InMemoryDBV1EntityController { 21 | constructor(protected dbService: InMemoryDBV1Service) { 22 | super(dbService); 23 | } 24 | } 25 | 26 | beforeEach(() => { 27 | service = inMemoryDBV1ServiceFactory()(); 28 | controller = new MockController(service); 29 | }); 30 | 31 | describe('get', () => { 32 | let spy; 33 | 34 | beforeEach(() => { 35 | spy = jest.spyOn(service, 'get'); 36 | }); 37 | 38 | test('should call service get spy when given valid id', () => { 39 | // act 40 | controller.get(1); 41 | // assert 42 | expect(spy).toHaveBeenCalledWith(1); 43 | }); 44 | }); 45 | 46 | describe('getMany', () => { 47 | test('should call service getMany spy when given list of ids', () => { 48 | // arrange 49 | const spy = spyOn(service, 'getMany'); 50 | const testEntityMock = [1, 2, 3]; 51 | // act 52 | controller.getMany(testEntityMock); 53 | // assert 54 | expect(spy).toHaveBeenCalledWith(testEntityMock); 55 | }); 56 | 57 | test('should call service getAll spy when no ids have been given', () => { 58 | // arrange 59 | const spy = spyOn(service, 'getAll'); 60 | // act 61 | controller.getMany(); 62 | // assert 63 | expect(spy).toHaveBeenCalledWith(); 64 | }); 65 | }); 66 | 67 | describe('create', () => { 68 | test('should call create when given a valid record', () => { 69 | // arrange 70 | const spy = jest.spyOn(service, 'create'); 71 | const testEntityMock = sampleRecords[0]; 72 | // act 73 | controller.create(testEntityMock); 74 | // assert 75 | expect(spy).toHaveBeenCalledWith(testEntityMock); 76 | }); 77 | 78 | test('should call create many when given valid records list', () => { 79 | // arrange 80 | const spy = jest.spyOn(service, 'createMany'); 81 | // act 82 | controller.create(sampleRecords); 83 | // assert 84 | expect(spy).toHaveBeenCalledWith(sampleRecords); 85 | }); 86 | }); 87 | 88 | describe('update', () => { 89 | let spy; 90 | 91 | beforeEach(() => { 92 | spy = jest.spyOn(service, 'update'); 93 | }); 94 | 95 | test('should call update when given a valid record and id', () => { 96 | // arrange 97 | const testEntityMock = { someField: 'DDD' }; 98 | // act 99 | controller.update(1, testEntityMock); 100 | // assert 101 | expect(spy).toHaveBeenCalledWith({ id: 1, ...testEntityMock }); 102 | }); 103 | }); 104 | 105 | describe('updateMany', () => { 106 | let spy; 107 | 108 | beforeEach(() => { 109 | spy = jest.spyOn(service, 'updateMany'); 110 | }); 111 | 112 | test('should call update many when given valid records list', () => { 113 | // arrange 114 | const testEntityMock = sampleRecords; 115 | // act 116 | controller.updateMany(testEntityMock); 117 | // assert 118 | expect(spy).toHaveBeenCalledWith(testEntityMock); 119 | }); 120 | }); 121 | 122 | describe('delete', () => { 123 | let spy; 124 | 125 | beforeEach(() => { 126 | spy = jest.spyOn(service, 'delete'); 127 | }); 128 | 129 | test('should call delete when give a valid id', () => { 130 | // act 131 | controller.delete(1); 132 | // assert 133 | expect(spy).toHaveBeenCalledWith(1); 134 | }); 135 | }); 136 | 137 | describe('deleteMany', () => { 138 | let spy; 139 | 140 | beforeEach(() => { 141 | spy = jest.spyOn(service, 'deleteMany'); 142 | }); 143 | 144 | test('should call delete many when given valid ids list', () => { 145 | // arrange 146 | const testEntityMock = [1, 2, 3]; 147 | // act 148 | controller.deleteMany(testEntityMock); 149 | // assert 150 | expect(spy).toHaveBeenCalledWith(testEntityMock); 151 | }); 152 | }); 153 | }); 154 | -------------------------------------------------------------------------------- /packages/in-memory-db/src/controllers/in-memory-db.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { InMemoryDBEntity } from '../interfaces'; 2 | import { inMemoryDBServiceFactory } from '../factories'; 3 | import { InMemoryDBEntityController } from './in-memory-db.controller'; 4 | import { InMemoryDBService } from '../services'; 5 | 6 | describe('In Memory DB Controller', () => { 7 | interface TestEntity extends InMemoryDBEntity { 8 | someField: string; 9 | } 10 | 11 | let controller: InMemoryDBEntityController; 12 | let service: InMemoryDBService; 13 | 14 | const sampleRecords: TestEntity[] = [ 15 | { id: '1', someField: 'AAA' }, 16 | { id: '2', someField: 'BBB' }, 17 | { id: '3', someField: 'CCC' }, 18 | ]; 19 | 20 | class MockController extends InMemoryDBEntityController { 21 | constructor(protected dbService: InMemoryDBService) { 22 | super(dbService); 23 | } 24 | } 25 | 26 | beforeEach(() => { 27 | service = inMemoryDBServiceFactory()(); 28 | controller = new MockController(service); 29 | }); 30 | 31 | describe('get', () => { 32 | let spy; 33 | 34 | beforeEach(() => { 35 | spy = jest.spyOn(service, 'get'); 36 | }); 37 | 38 | test('should call service get spy when given valid id', () => { 39 | // act 40 | controller.get('1'); 41 | // assert 42 | expect(spy).toHaveBeenCalledWith('1'); 43 | }); 44 | }); 45 | 46 | describe('getMany', () => { 47 | test('should call service getMany spy when given list of ids', () => { 48 | // arrange 49 | const spy = spyOn(service, 'getMany'); 50 | const testEntityMock = ['1', '2', '3']; 51 | // act 52 | controller.getMany(testEntityMock); 53 | // assert 54 | expect(spy).toHaveBeenCalledWith(testEntityMock); 55 | }); 56 | 57 | test('should call service getAll spy when no ids have been given', () => { 58 | // arrange 59 | const spy = spyOn(service, 'getAll'); 60 | // act 61 | controller.getMany(); 62 | // assert 63 | expect(spy).toHaveBeenCalledWith(); 64 | }); 65 | }); 66 | 67 | describe('create', () => { 68 | test('should call create when given a valid record', () => { 69 | // arrange 70 | const spy = jest.spyOn(service, 'create'); 71 | const testEntityMock = sampleRecords[0]; 72 | // act 73 | controller.create(testEntityMock); 74 | // assert 75 | expect(spy).toHaveBeenCalledWith(testEntityMock); 76 | }); 77 | 78 | test('should call create many when given valid records list', () => { 79 | // arrange 80 | const spy = jest.spyOn(service, 'createMany'); 81 | // act 82 | controller.create(sampleRecords); 83 | // assert 84 | expect(spy).toHaveBeenCalledWith(sampleRecords); 85 | }); 86 | }); 87 | 88 | describe('update', () => { 89 | let spy; 90 | 91 | beforeEach(() => { 92 | spy = jest.spyOn(service, 'update'); 93 | }); 94 | 95 | test('should call update when given a valid record and id', () => { 96 | // arrange 97 | const testEntityMock = { someField: 'DDD' }; 98 | // act 99 | controller.update('1', testEntityMock); 100 | // assert 101 | expect(spy).toHaveBeenCalledWith({ id: '1', ...testEntityMock }); 102 | }); 103 | }); 104 | 105 | describe('updateMany', () => { 106 | let spy; 107 | 108 | beforeEach(() => { 109 | spy = jest.spyOn(service, 'updateMany'); 110 | }); 111 | 112 | test('should call update many when given valid records list', () => { 113 | // arrange 114 | const testEntityMock = sampleRecords; 115 | // act 116 | controller.updateMany(testEntityMock); 117 | // assert 118 | expect(spy).toHaveBeenCalledWith(testEntityMock); 119 | }); 120 | }); 121 | 122 | describe('delete', () => { 123 | let spy; 124 | 125 | beforeEach(() => { 126 | spy = jest.spyOn(service, 'delete'); 127 | }); 128 | 129 | test('should call delete when given a valid id', () => { 130 | // act 131 | controller.delete('1'); 132 | // assert 133 | expect(spy).toHaveBeenCalledWith('1'); 134 | }); 135 | }); 136 | 137 | describe('deleteMany', () => { 138 | let spy; 139 | 140 | beforeEach(() => { 141 | spy = jest.spyOn(service, 'deleteMany'); 142 | }); 143 | 144 | test('should call delete many when given valid ids list', () => { 145 | // arrange 146 | const testEntityMock = ['1', '2', '3']; 147 | // act 148 | controller.deleteMany(testEntityMock); 149 | // assert 150 | expect(spy).toHaveBeenCalledWith(testEntityMock); 151 | }); 152 | }); 153 | }); 154 | -------------------------------------------------------------------------------- /packages/in-memory-db/src/v1/controllers/in-memory-db-async.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { InMemoryDBV1Entity } from '../interfaces'; 2 | import { inMemoryDBV1ServiceFactory } from '../factories'; 3 | import { InMemoryDBV1EntityAsyncController } from './in-memory-db-async.controller'; 4 | import { InMemoryDBV1Service } from '../services'; 5 | 6 | describe('In Memory DB Async Controller', () => { 7 | interface TestEntity extends InMemoryDBV1Entity { 8 | someField: string; 9 | } 10 | 11 | let controller: InMemoryDBV1EntityAsyncController; 12 | let service: InMemoryDBV1Service; 13 | 14 | const sampleRecords: TestEntity[] = [ 15 | { id: 1, someField: 'AAA' }, 16 | { id: 2, someField: 'BBB' }, 17 | { id: 3, someField: 'CCC' }, 18 | ]; 19 | 20 | class MockController extends InMemoryDBV1EntityAsyncController { 21 | constructor(protected dbService: InMemoryDBV1Service) { 22 | super(dbService); 23 | } 24 | } 25 | 26 | beforeEach(() => { 27 | service = inMemoryDBV1ServiceFactory()(); 28 | controller = new MockController(service); 29 | }); 30 | 31 | describe('get', () => { 32 | let spy; 33 | 34 | beforeEach(() => { 35 | spy = jest.spyOn(service, 'getAsync'); 36 | }); 37 | 38 | test('should call service getAsync spy when given valid id', () => { 39 | // act 40 | controller.get(1); 41 | // assert 42 | expect(spy).toHaveBeenCalledWith(1); 43 | }); 44 | }); 45 | 46 | describe('getMany', () => { 47 | test('should call service getManyAsync spy when given list of ids', () => { 48 | // arrange 49 | const spy = spyOn(service, 'getManyAsync'); 50 | const testEntityMock = [1, 2, 3]; 51 | // act 52 | controller.getMany(testEntityMock); 53 | // assert 54 | expect(spy).toHaveBeenCalledWith(testEntityMock); 55 | }); 56 | 57 | test('should call service getAllAsync spy when no ids have been given', () => { 58 | // arrange 59 | const spy = spyOn(service, 'getAllAsync'); 60 | // act 61 | controller.getMany(); 62 | // assert 63 | expect(spy).toHaveBeenCalledWith(); 64 | }); 65 | }); 66 | 67 | describe('create', () => { 68 | test('should call createAsync when given a valid record', () => { 69 | // arrange 70 | const spy = jest.spyOn(service, 'createAsync'); 71 | const testEntityMock = sampleRecords[0]; 72 | // act 73 | controller.create(testEntityMock); 74 | // assert 75 | expect(spy).toHaveBeenCalledWith(testEntityMock); 76 | }); 77 | 78 | test('should call createManyAsync when given valid records list', () => { 79 | // arrange 80 | const spy = jest.spyOn(service, 'createManyAsync'); 81 | // act 82 | controller.create(sampleRecords); 83 | // assert 84 | expect(spy).toHaveBeenCalledWith(sampleRecords); 85 | }); 86 | }); 87 | 88 | describe('update', () => { 89 | let spy; 90 | 91 | beforeEach(() => { 92 | spy = jest.spyOn(service, 'updateAsync'); 93 | }); 94 | 95 | test('should call updateAsync when given a valid record and id', () => { 96 | // arrange 97 | const testEntityMock = { someField: 'DDD' }; 98 | // act 99 | controller.update(1, testEntityMock); 100 | // assert 101 | expect(spy).toHaveBeenCalledWith({ id: 1, ...testEntityMock }); 102 | }); 103 | }); 104 | 105 | describe('updateMany', () => { 106 | let spy; 107 | 108 | beforeEach(() => { 109 | spy = jest.spyOn(service, 'updateManyAsync'); 110 | }); 111 | 112 | test('should call updateManyAsync when given valid records list', () => { 113 | // arrange 114 | const testEntityMock = sampleRecords; 115 | // act 116 | controller.updateMany(testEntityMock); 117 | // assert 118 | expect(spy).toHaveBeenCalledWith(testEntityMock); 119 | }); 120 | }); 121 | 122 | describe('delete', () => { 123 | let spy; 124 | 125 | beforeEach(() => { 126 | spy = jest.spyOn(service, 'deleteAsync'); 127 | }); 128 | 129 | test('should call deleteAsync when give a valid id', () => { 130 | // act 131 | controller.delete(1); 132 | // assert 133 | expect(spy).toHaveBeenCalledWith(1); 134 | }); 135 | }); 136 | 137 | describe('deleteMany', () => { 138 | let spy; 139 | 140 | beforeEach(() => { 141 | spy = jest.spyOn(service, 'deleteManyAsync'); 142 | }); 143 | 144 | test('should call deleteManyAsync when given valid ids list', () => { 145 | // arrange 146 | const testEntityMock = [1, 2, 3]; 147 | // act 148 | controller.deleteMany(testEntityMock); 149 | // assert 150 | expect(spy).toHaveBeenCalledWith(testEntityMock); 151 | }); 152 | }); 153 | }); 154 | -------------------------------------------------------------------------------- /packages/in-memory-db/src/controllers/in-memory-db-async.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { InMemoryDBEntity } from '../interfaces'; 2 | import { inMemoryDBServiceFactory } from '../factories'; 3 | import { InMemoryDBEntityAsyncController } from './in-memory-db-async.controller'; 4 | import { InMemoryDBService } from '../services'; 5 | 6 | describe('In Memory DB Async Controller', () => { 7 | interface TestEntity extends InMemoryDBEntity { 8 | someField: string; 9 | } 10 | 11 | let controller: InMemoryDBEntityAsyncController; 12 | let service: InMemoryDBService; 13 | 14 | const sampleRecords: TestEntity[] = [ 15 | { id: '1', someField: 'AAA' }, 16 | { id: '2', someField: 'BBB' }, 17 | { id: '3', someField: 'CCC' }, 18 | ]; 19 | 20 | class MockController extends InMemoryDBEntityAsyncController { 21 | constructor(protected dbService: InMemoryDBService) { 22 | super(dbService); 23 | } 24 | } 25 | 26 | beforeEach(() => { 27 | service = inMemoryDBServiceFactory()(); 28 | controller = new MockController(service); 29 | }); 30 | 31 | describe('get', () => { 32 | let spy; 33 | 34 | beforeEach(() => { 35 | spy = jest.spyOn(service, 'getAsync'); 36 | }); 37 | 38 | test('should call service getAsync spy when given valid id', () => { 39 | // act 40 | controller.get('1'); 41 | // assert 42 | expect(spy).toHaveBeenCalledWith('1'); 43 | }); 44 | }); 45 | 46 | describe('getMany', () => { 47 | test('should call service getManyAsync spy when given list of ids', () => { 48 | // arrange 49 | const spy = spyOn(service, 'getManyAsync'); 50 | const testEntityMock = ['1', '2', '3']; 51 | // act 52 | controller.getMany(testEntityMock); 53 | // assert 54 | expect(spy).toHaveBeenCalledWith(testEntityMock); 55 | }); 56 | 57 | test('should call service getAllAsync spy when no ids have been given', () => { 58 | // arrange 59 | const spy = spyOn(service, 'getAllAsync'); 60 | // act 61 | controller.getMany(); 62 | // assert 63 | expect(spy).toHaveBeenCalledWith(); 64 | }); 65 | }); 66 | 67 | describe('create', () => { 68 | test('should call createAsync when given a valid record', () => { 69 | // arrange 70 | const spy = jest.spyOn(service, 'createAsync'); 71 | const testEntityMock = sampleRecords[0]; 72 | // act 73 | controller.create(testEntityMock); 74 | // assert 75 | expect(spy).toHaveBeenCalledWith(testEntityMock); 76 | }); 77 | 78 | test('should call createManyAsync when given valid records list', () => { 79 | // arrange 80 | const spy = jest.spyOn(service, 'createManyAsync'); 81 | // act 82 | controller.create(sampleRecords); 83 | // assert 84 | expect(spy).toHaveBeenCalledWith(sampleRecords); 85 | }); 86 | }); 87 | 88 | describe('update', () => { 89 | let spy; 90 | 91 | beforeEach(() => { 92 | spy = jest.spyOn(service, 'updateAsync'); 93 | }); 94 | 95 | test('should call updateAsync when given a valid record and id', () => { 96 | // arrange 97 | const testEntityMock = { someField: 'DDD' }; 98 | // act 99 | controller.update('1', testEntityMock); 100 | // assert 101 | expect(spy).toHaveBeenCalledWith({ id: '1', ...testEntityMock }); 102 | }); 103 | }); 104 | 105 | describe('updateMany', () => { 106 | let spy; 107 | 108 | beforeEach(() => { 109 | spy = jest.spyOn(service, 'updateManyAsync'); 110 | }); 111 | 112 | test('should call updateManyAsync when given valid records list', () => { 113 | // arrange 114 | const testEntityMock = sampleRecords; 115 | // act 116 | controller.updateMany(testEntityMock); 117 | // assert 118 | expect(spy).toHaveBeenCalledWith(testEntityMock); 119 | }); 120 | }); 121 | 122 | describe('delete', () => { 123 | let spy; 124 | 125 | beforeEach(() => { 126 | spy = jest.spyOn(service, 'deleteAsync'); 127 | }); 128 | 129 | test('should call deleteAsync when give a valid id', () => { 130 | // act 131 | controller.delete('1'); 132 | // assert 133 | expect(spy).toHaveBeenCalledWith('1'); 134 | }); 135 | }); 136 | 137 | describe('deleteMany', () => { 138 | let spy; 139 | 140 | beforeEach(() => { 141 | spy = jest.spyOn(service, 'deleteManyAsync'); 142 | }); 143 | 144 | test('should call deleteManyAsync when given valid ids list', () => { 145 | // arrange 146 | const testEntityMock = ['1', '2', '3']; 147 | // act 148 | controller.deleteMany(testEntityMock); 149 | // assert 150 | expect(spy).toHaveBeenCalledWith(testEntityMock); 151 | }); 152 | }); 153 | }); 154 | -------------------------------------------------------------------------------- /API_V1.md: -------------------------------------------------------------------------------- 1 | # API Documentation - V1 2 | 3 | ## `InMemoryDBV1Service` 4 | 5 | This is the service that provides the in-memory database. All methods interact with a `records` array and implement `generics` to provide type-safety and intellisense based on the `T extends InMemoryDBV1Entity` passed in. 6 | 7 | ### Public Methods 8 | 9 | **`public create(record: Partial): T`** 10 | 11 | This method takes in a `Partial` as we do not always know the `id` for a record when we are creating. If we leave off the `id` property the service will automatically generate an `id` for us. Upon successful creation, the method returns the record with the newly generated `id`. 12 | 13 | Example Usage: 14 | 15 | ```typescript 16 | const newUser = this.userService.create({ 17 | firstName: 'Some', 18 | lastName: 'Person', 19 | }); 20 | 21 | console.log({ newUser }); 22 | 23 | // logs out 24 | // { 25 | // newUser: { 26 | // id: 1, 27 | // firstName: 'Some', 28 | // lastName: 'Person, 29 | // } 30 | // } 31 | ``` 32 | 33 | **`public createMany(records: Array>): T[]`** 34 | 35 | This method takes in an array of `Partial` as we do not always know the `id` for records when we are creating. If we leave off the `id` properties the service will automatically generate `id`s for us. Upon successful creation, the method returns the an array of records with the newly generated `id`s. 36 | 37 | Example Usage: 38 | 39 | ```typescript 40 | const recordsToCreate = [ 41 | { 42 | firstName: 'Some', 43 | lastName: 'Person', 44 | }, 45 | { 46 | firstName: 'Other', 47 | lastName: 'Person', 48 | }, 49 | ]; 50 | 51 | const newUsers = this.userService.createMany(recordsToCreate); 52 | 53 | console.log({ newUsers }); 54 | 55 | // logs out 56 | // { 57 | // newUsers: [{ 58 | // id: 1, 59 | // firstName: 'Some', 60 | // lastName: 'Person, 61 | // }, 62 | // { 63 | // id: 2, 64 | // firstName: 'Other', 65 | // lastName: 'Person, 66 | // }] 67 | // } 68 | ``` 69 | 70 | **`public update(record: T): void`** 71 | 72 | This method takes in a `T` record object and updates the record in the `records` array based on the `id` in the object. This method does not return a value. 73 | 74 | Example Usage: 75 | 76 | ```typescript 77 | this.userService.update({ 78 | id: 1, 79 | firstName: 'Other', 80 | lastName: 'Person', 81 | }); 82 | ``` 83 | 84 | **`public delete(id: number): void`** 85 | 86 | This method takes in a `id: number` and deletes the record from the `records` array based on the `id` in the object. This method does not return a value. 87 | 88 | Example Usage: 89 | 90 | ```typescript 91 | this.userService.delete(1); 92 | ``` 93 | 94 | **`public get(id: number): T`** 95 | 96 | This method takes in a `id: number` and returns the record from the `records` array based on the `id` in the object. 97 | 98 | Example Usage: 99 | 100 | ```typescript 101 | const foundUser = this.userService.get(1); 102 | 103 | console.log({ foundUser }); 104 | 105 | // logs out 106 | // { 107 | // foundUser: { 108 | // id:1, 109 | // firstName: 'Some', 110 | // lastName: 'Person' 111 | // } 112 | // } 113 | ``` 114 | 115 | **`public getAll(): T[]`** 116 | 117 | This method has no parameters and returns the all from the `records` array. 118 | 119 | Example Usage: 120 | 121 | ```typescript 122 | const allUsers = this.userService.getAll(); 123 | 124 | console.log({ allUsers }); 125 | 126 | // logs out 127 | // { 128 | // allUsers: [ 129 | // { 130 | // id: 1, 131 | // firstName: 'Some', 132 | // lastName: 'Person' 133 | // }, 134 | // { 135 | // id: 2, 136 | // firstName: 'Other', 137 | // lastName: 'Person' 138 | // } 139 | // ]; 140 | // } 141 | ``` 142 | 143 | **`public query(predicate: (record: T) => boolean): T[]`** 144 | 145 | This method has takes in a `record: T` predicate and returns all from the `records` array that meet that predicate's requirements. 146 | 147 | Example Usage: 148 | 149 | ```typescript 150 | const foundUsers = this.userService.query( 151 | (record) => record.lastName === 'Person', 152 | ); 153 | 154 | console.log({ foundUsers }); 155 | 156 | // logs out 157 | // { 158 | // allUsers: [ 159 | // { 160 | // id: 1, 161 | // firstName: 'Some', 162 | // lastName: 'Person' 163 | // }, 164 | // { 165 | // id: 2, 166 | // firstName: 'Other', 167 | // lastName: 'Person' 168 | // } 169 | // ]; 170 | // } 171 | ``` 172 | 173 | ### Public Properties 174 | 175 | - `records: T[]` - This is the in-memory array used in all crud and read operations for the service. Please access with care. 176 | 177 | ## `InMemoryDBV1Entity` 178 | 179 | This is an interface used by the `InMemoryDBV1Service` for intellisense and type-safety. Do not use this interface directly. Rather, implement your own `interface` that `extends` this. 180 | 181 | ```typescript 182 | export interface InMemoryDBV1Entity { 183 | id: number; 184 | } 185 | ``` 186 | -------------------------------------------------------------------------------- /API.md: -------------------------------------------------------------------------------- 1 | # API Documentation 2 | 3 | ## `InMemoryDBService` 4 | 5 | This is the service that provides the in-memory database. All methods interact with a `records` array and implement `generics` to provide type-safety and intellisense based on the `T extends InMemoryEntity` passed in. 6 | 7 | ### Public Methods 8 | 9 | **`public create(record: Partial, getNextId?: () => string): T`** 10 | 11 | This method takes in a `Partial` as we do not always know the `id` for a record when we are creating. If we leave off the `id` property the service will automatically generate an `id` for us. Upon successful creation, the method returns the record with the newly generated `id`. An optional parameter of `getNextId` can be used to pass a function that returns a `string` and will be used by the service to get the next id. By default this uses the `uuid` npm package. 12 | 13 | Example Usage: 14 | 15 | ```typescript 16 | const newUser = this.userService.create({ 17 | firstName: 'Some', 18 | lastName: 'Person', 19 | }); 20 | 21 | console.log({ newUser }); 22 | 23 | // logs out 24 | // { 25 | // newUser: { 26 | // id: 1, 27 | // firstName: 'Some', 28 | // lastName: 'Person, 29 | // } 30 | // } 31 | ``` 32 | 33 | **`public createMany(records: Array>, getNextId?: () => string): T[]`** 34 | 35 | This method takes in an array of `Partial` as we do not always know the `id` for records when we are creating. If we leave off the `id` properties the service will automatically generate `id`s for us. Upon successful creation, the method returns the an array of records with the newly generated `id`s. An optional parameter of `getNextId` can be used to pass a function that returns a `string` and will be used by the service to get the next id. By default this uses the `uuid` npm package. 36 | 37 | Example Usage: 38 | 39 | ```typescript 40 | const recordsToCreate = [ 41 | { 42 | firstName: 'Some', 43 | lastName: 'Person', 44 | }, 45 | { 46 | firstName: 'Other', 47 | lastName: 'Person', 48 | }, 49 | ]; 50 | 51 | const newUsers = this.userService.createMany(recordsToCreate); 52 | 53 | console.log({ newUsers }); 54 | 55 | // logs out 56 | // { 57 | // newUsers: [{ 58 | // id: 1, 59 | // firstName: 'Some', 60 | // lastName: 'Person, 61 | // }, 62 | // { 63 | // id: 2, 64 | // firstName: 'Other', 65 | // lastName: 'Person, 66 | // }] 67 | // } 68 | ``` 69 | 70 | **`public update(record: T): void`** 71 | 72 | This method takes in a `T` record object and updates the record in the `records` array based on the `id` in the object. This method does not return a value. 73 | 74 | Example Usage: 75 | 76 | ```typescript 77 | this.userService.update({ 78 | id: 1, 79 | firstName: 'Other', 80 | lastName: 'Person', 81 | }); 82 | ``` 83 | 84 | **`public delete(id: string): void`** 85 | 86 | This method takes in a `id: string` and deletes the record from the `records` array based on the `id` in the object. This method does not return a value. 87 | 88 | Example Usage: 89 | 90 | ```typescript 91 | this.userService.delete('1'); 92 | ``` 93 | 94 | **`public get(id: string): T`** 95 | 96 | This method takes in a `id: string` and returns the record from the `records` array based on the `id` in the object. 97 | 98 | Example Usage: 99 | 100 | ```typescript 101 | const foundUser = this.userService.get('1'); 102 | 103 | console.log({ foundUser }); 104 | 105 | // logs out 106 | // { 107 | // foundUser: { 108 | // id: '1', 109 | // firstName: 'Some', 110 | // lastName: 'Person' 111 | // } 112 | // } 113 | ``` 114 | 115 | **`public getAll(): T[]`** 116 | 117 | This method has no parameters and returns the all from the `records` array. 118 | 119 | Example Usage: 120 | 121 | ```typescript 122 | const allUsers = this.userService.getAll(); 123 | 124 | console.log({ allUsers }); 125 | 126 | // logs out 127 | // { 128 | // allUsers: [ 129 | // { 130 | // id: '1', 131 | // firstName: 'Some', 132 | // lastName: 'Person' 133 | // }, 134 | // { 135 | // id: '2', 136 | // firstName: 'Other', 137 | // lastName: 'Person' 138 | // } 139 | // ]; 140 | // } 141 | ``` 142 | 143 | **`public query(predicate: (record: T) => boolean): T[]`** 144 | 145 | This method has takes in a `record: T` predicate and returns all from the `records` array that meet that predicate's requirements. 146 | 147 | Example Usage: 148 | 149 | ```typescript 150 | const foundUsers = this.userService.query( 151 | (record) => record.lastName === 'Person', 152 | ); 153 | 154 | console.log({ foundUsers }); 155 | 156 | // logs out 157 | // { 158 | // allUsers: [ 159 | // { 160 | // id: '1', 161 | // firstName: 'Some', 162 | // lastName: 'Person' 163 | // }, 164 | // { 165 | // id: '2', 166 | // firstName: 'Other', 167 | // lastName: 'Person' 168 | // } 169 | // ]; 170 | // } 171 | ``` 172 | 173 | ### Public Properties 174 | 175 | - `records: T[]` - This is the in-memory array used in all crud and read operations for the service. Please access with care. 176 | 177 | ## `InMemoryDBEntity` 178 | 179 | This is an interface used by the `InMemoryDBService` for intellisense and type-safety. Do not use this interface directly. Rather, implement your own `interface` that `extends` this. 180 | 181 | ```typescript 182 | export interface InMemoryDBEntity { 183 | id: string; 184 | } 185 | ``` 186 | -------------------------------------------------------------------------------- /packages/docs/docs/IMDB-apidoc.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: in-memory-db-apidoc 3 | title: API Documentation 4 | --- 5 | 6 | ## `InMemoryDBService` 7 | 8 | This is the service that provides the in-memory database. All methods interact with a `records` array and implement `generics` to provide type-safety and intellisense based on the `T extends InMemoryEntity` passed in. 9 | 10 | ### Public Methods 11 | 12 | **`public create(record: Partial, getNextId?: () => string): T`** 13 | 14 | This method takes in a `Partial` as we do not always know the `id` for a record when we are creating. If we leave off the `id` property the service will automatically generate an `id` for us. Upon successful creation, the method returns the record with the newly generated `id`. An optional parameter of `getNextId` can be used to pass a function that returns a `string` and will be used by the service to get the next id. By default this uses the `uuid` npm package. 15 | 16 | Example Usage: 17 | 18 | ```typescript 19 | const newUser = this.userService.create({ 20 | firstName: 'Some', 21 | lastName: 'Person', 22 | }); 23 | 24 | console.log({ newUser }); 25 | 26 | // logs out 27 | // { 28 | // newUser: { 29 | // id: 1, 30 | // firstName: 'Some', 31 | // lastName: 'Person, 32 | // } 33 | // } 34 | ``` 35 | 36 | **`public createMany(records: Array>, getNextId?: () => string): T[]`** 37 | 38 | This method takes in an array of `Partial` as we do not always know the `id` for records when we are creating. If we leave off the `id` properties the service will automatically generate `id`s for us. Upon successful creation, the method returns the an array of records with the newly generated `id`s. An optional parameter of `getNextId` can be used to pass a function that returns a `string` and will be used by the service to get the next id. By default this uses the `uuid` npm package. 39 | 40 | Example Usage: 41 | 42 | ```typescript 43 | const recordsToCreate = [ 44 | { 45 | firstName: 'Some', 46 | lastName: 'Person', 47 | }, 48 | { 49 | firstName: 'Other', 50 | lastName: 'Person', 51 | }, 52 | ]; 53 | 54 | const newUsers = this.userService.createMany(recordsToCreate); 55 | 56 | console.log({ newUsers }); 57 | 58 | // logs out 59 | // { 60 | // newUsers: [{ 61 | // id: 1, 62 | // firstName: 'Some', 63 | // lastName: 'Person, 64 | // }, 65 | // { 66 | // id: 2, 67 | // firstName: 'Other', 68 | // lastName: 'Person, 69 | // }] 70 | // } 71 | ``` 72 | 73 | **`public update(record: T): void`** 74 | 75 | This method takes in a `T` record object and updates the record in the `records` array based on the `id` in the object. This method does not return a value. 76 | 77 | Example Usage: 78 | 79 | ```typescript 80 | this.userService.update({ 81 | id: 1, 82 | firstName: 'Other', 83 | lastName: 'Person', 84 | }); 85 | ``` 86 | 87 | **`public delete(id: string): void`** 88 | 89 | This method takes in a `id: string` and deletes the record from the `records` array based on the `id` in the object. This method does not return a value. 90 | 91 | Example Usage: 92 | 93 | ```typescript 94 | this.userService.delete('1'); 95 | ``` 96 | 97 | **`public get(id: string): T`** 98 | 99 | This method takes in a `id: string` and returns the record from the `records` array based on the `id` in the object. 100 | 101 | Example Usage: 102 | 103 | ```typescript 104 | const foundUser = this.userService.get('1'); 105 | 106 | console.log({ foundUser }); 107 | 108 | // logs out 109 | // { 110 | // foundUser: { 111 | // id: '1', 112 | // firstName: 'Some', 113 | // lastName: 'Person' 114 | // } 115 | // } 116 | ``` 117 | 118 | **`public getAll(): T[]`** 119 | 120 | This method has no parameters and returns the all from the `records` array. 121 | 122 | Example Usage: 123 | 124 | ```typescript 125 | const allUsers = this.userService.getAll(); 126 | 127 | console.log({ allUsers }); 128 | 129 | // logs out 130 | // { 131 | // allUsers: [ 132 | // { 133 | // id: '1', 134 | // firstName: 'Some', 135 | // lastName: 'Person' 136 | // }, 137 | // { 138 | // id: '2', 139 | // firstName: 'Other', 140 | // lastName: 'Person' 141 | // } 142 | // ]; 143 | // } 144 | ``` 145 | 146 | **`public query(predicate: (record: T) => boolean): T[]`** 147 | 148 | This method has takes in a `record: T` predicate and returns all from the `records` array that meet that predicate's requirements. 149 | 150 | Example Usage: 151 | 152 | ```typescript 153 | const foundUsers = this.userService.query( 154 | (record) => record.lastName === 'Person', 155 | ); 156 | 157 | console.log({ foundUsers }); 158 | 159 | // logs out 160 | // { 161 | // allUsers: [ 162 | // { 163 | // id: '1', 164 | // firstName: 'Some', 165 | // lastName: 'Person' 166 | // }, 167 | // { 168 | // id: '2', 169 | // firstName: 'Other', 170 | // lastName: 'Person' 171 | // } 172 | // ]; 173 | // } 174 | ``` 175 | 176 | ### Public Properties 177 | 178 | - `records: T[]` - This is the in-memory array used in all crud and read operations for the service. Please access with care. 179 | 180 | ## `InMemoryDBEntity` 181 | 182 | This is an interface used by the `InMemoryDBService` for intellisense and type-safety. Do not use this interface directly. Rather, implement your own `interface` that `extends` this. 183 | 184 | ```typescript 185 | export interface InMemoryDBEntity { 186 | id: string; 187 | } 188 | ``` -------------------------------------------------------------------------------- /packages/in-memory-db/src/schematics/utils/change.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | import * as ts from 'typescript'; 3 | import { Tree, UpdateRecorder } from '@angular-devkit/schematics'; 4 | import { Path } from '@angular-devkit/core'; 5 | 6 | /* istanbul ignore file */ 7 | /** 8 | * @license 9 | * Copyright Google Inc. All Rights Reserved. 10 | * 11 | * Use of this source code is governed by an MIT-style license that can be 12 | * found in the LICENSE file at https://angular.io/license 13 | */ 14 | export interface Host { 15 | write(path: string, content: string): Promise; 16 | read(path: string): Promise; 17 | } 18 | 19 | export interface Change { 20 | apply(host: Host): Promise; 21 | 22 | // The file this change should be applied to. Some changes might not apply to 23 | // a file (maybe the config). 24 | readonly path: string | null; 25 | 26 | // The order this change should be applied. Normally the position inside the file. 27 | // Changes are applied from the bottom of a file to the top. 28 | readonly order: number; 29 | 30 | // The description of this change. This will be outputted in a dry or verbose run. 31 | readonly description: string; 32 | } 33 | 34 | /** 35 | * An operation that does nothing. 36 | */ 37 | export class NoopChange implements Change { 38 | description = 'No operation.'; 39 | order = Infinity; 40 | path = null; 41 | apply() { 42 | return Promise.resolve(); 43 | } 44 | } 45 | 46 | /** 47 | * Will add text to the source code. 48 | */ 49 | export class InsertChange implements Change { 50 | order: number; 51 | description: string; 52 | 53 | constructor(public path: string, public pos: number, public toAdd: string) { 54 | if (pos < 0) { 55 | throw new Error('Negative positions are invalid'); 56 | } 57 | this.description = `Inserted ${toAdd} into position ${pos} of ${path}`; 58 | this.order = pos; 59 | } 60 | 61 | /** 62 | * This method does not insert spaces if there is none in the original string. 63 | */ 64 | apply(host: Host) { 65 | return host.read(this.path).then((content) => { 66 | const prefix = content.substring(0, this.pos); 67 | const suffix = content.substring(this.pos); 68 | 69 | return host.write(this.path, `${prefix}${this.toAdd}${suffix}`); 70 | }); 71 | } 72 | } 73 | 74 | /** 75 | * Will remove text from the source code. 76 | */ 77 | export class RemoveChange implements Change { 78 | order: number; 79 | description: string; 80 | 81 | constructor(public path: string, public pos: number, public end: number) { 82 | if (pos < 0 || end < 0) { 83 | throw new Error('Negative positions are invalid'); 84 | } 85 | this.description = `Removed text in position ${pos} to ${end} of ${path}`; 86 | this.order = pos; 87 | } 88 | 89 | apply(host: Host): Promise { 90 | return host.read(this.path).then((content) => { 91 | const prefix = content.substring(0, this.pos); 92 | const suffix = content.substring(this.end); 93 | 94 | // TODO: throw error if toRemove doesn't match removed string. 95 | return host.write(this.path, `${prefix}${suffix}`); 96 | }); 97 | } 98 | } 99 | 100 | /** 101 | * Will replace text from the source code. 102 | */ 103 | export class ReplaceChange implements Change { 104 | order: number; 105 | description: string; 106 | 107 | constructor( 108 | public path: string, 109 | public pos: number, 110 | public oldText: string, 111 | public newText: string, 112 | ) { 113 | if (pos < 0) { 114 | throw new Error('Negative positions are invalid'); 115 | } 116 | this.description = `Replaced ${oldText} into position ${pos} of ${path} with ${newText}`; 117 | this.order = pos; 118 | } 119 | 120 | apply(host: Host): Promise { 121 | return host.read(this.path).then((content) => { 122 | const prefix = content.substring(0, this.pos); 123 | const suffix = content.substring(this.pos + this.oldText.length); 124 | const text = content.substring(this.pos, this.pos + this.oldText.length); 125 | 126 | if (text !== this.oldText) { 127 | return Promise.reject( 128 | new Error(`Invalid replace: "${text}" != "${this.oldText}".`), 129 | ); 130 | } 131 | 132 | // TODO: throw error if oldText doesn't match removed string. 133 | return host.write(this.path, `${prefix}${this.newText}${suffix}`); 134 | }); 135 | } 136 | } 137 | 138 | export function createReplaceChange( 139 | sourceFile: ts.SourceFile, 140 | node: ts.Node, 141 | oldText: string, 142 | newText: string, 143 | ): ReplaceChange { 144 | return new ReplaceChange( 145 | sourceFile.fileName, 146 | node.getStart(sourceFile), 147 | oldText, 148 | newText, 149 | ); 150 | } 151 | 152 | export function createChangeRecorder( 153 | tree: Tree, 154 | path: string, 155 | changes: Change[], 156 | ): UpdateRecorder { 157 | const recorder = tree.beginUpdate(path); 158 | for (const change of changes) { 159 | if (change instanceof InsertChange) { 160 | recorder.insertLeft(change.pos, change.toAdd); 161 | } else if (change instanceof RemoveChange) { 162 | recorder.remove(change.pos, change.end - change.pos); 163 | } else if (change instanceof ReplaceChange) { 164 | recorder.remove(change.pos, change.oldText.length); 165 | recorder.insertLeft(change.pos, change.newText); 166 | } 167 | } 168 | return recorder; 169 | } 170 | 171 | export function commitChanges(tree: Tree, path: string, changes: Change[]) { 172 | if (changes.length === 0) { 173 | return false; 174 | } 175 | 176 | const recorder = createChangeRecorder(tree, path, changes); 177 | tree.commitUpdate(recorder); 178 | return true; 179 | } 180 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: CI Checks 2 | on: 3 | pull_request: 4 | branches: [master] 5 | push: 6 | branches: [master] 7 | 8 | jobs: 9 | setup: 10 | runs-on: ${{ matrix.os }} 11 | strategy: 12 | matrix: 13 | os: [ubuntu-latest, windows-latest] 14 | node-version: [12.x] 15 | steps: 16 | - name: Use Node.js ${{ matrix.node-version }} 17 | uses: actions/setup-node@v1 18 | with: 19 | node-version: ${{ matrix.node-version }} 20 | - uses: actions/checkout@v2 21 | with: 22 | fetch-depth: 3 23 | - name: Get origin master 24 | run: | 25 | git fetch --no-tags --prune --depth=1 origin +refs/heads/master:refs/remotes/origin/master 26 | - uses: actions/cache@v1 27 | id: workspace-cache 28 | with: 29 | path: node_modules 30 | key: ${{ runner.os }}-${{ matrix.node-version }}-workspace-${{ hashFiles('**/yarn.lock') }} 31 | restore-keys: | 32 | ${{ runner.os }}-${{ matrix.node-version }}-workspace- 33 | - name: Install Dependencies 34 | run: yarn install --frozen-lockfile 35 | lint: 36 | runs-on: ${{ matrix.os }} 37 | 38 | needs: [setup] 39 | strategy: 40 | matrix: 41 | os: [ubuntu-latest, windows-latest] 42 | node-version: [12.x] 43 | 44 | steps: 45 | - name: Use Node.js ${{ matrix.node-version }} 46 | uses: actions/setup-node@v1 47 | with: 48 | node-version: ${{ matrix.node-version }} 49 | - uses: actions/checkout@v2 50 | with: 51 | fetch-depth: 3 52 | - name: Get origin master 53 | if: github.event_name == 'pull_request' 54 | run: | 55 | git fetch --no-tags --prune --depth=1 origin +refs/heads/master:refs/remotes/origin/master 56 | - uses: actions/cache@v1 57 | id: workspace-cache 58 | with: 59 | path: node_modules 60 | key: ${{ runner.os }}-${{ matrix.node-version }}-workspace-${{ hashFiles('**/yarn.lock') }} 61 | restore-keys: | 62 | ${{ runner.os }}-${{ matrix.node-version }}-workspace- 63 | - name: Run On Master (Linux) 64 | if: github.event_name == 'push' && runner.os == 'Linux' 65 | run: yarn affected:lint --base=$GITHUB_REF~1 --head=$GITHUB_SHA --parallel 66 | - name: Run On Master (Windows) 67 | if: github.event_name == 'push' && runner.os == 'Windows' 68 | run: yarn affected:lint --base=${env:GITHUB_REF}~1 --head=${env:GITHUB_SHA} --parallel 69 | - name: Run on PR 70 | if: github.event_name == 'pull_request' 71 | run: yarn affected:lint --base=origin/master --head=HEAD --parallel 72 | 73 | build: 74 | runs-on: ${{ matrix.os }} 75 | 76 | needs: [setup] 77 | 78 | strategy: 79 | matrix: 80 | os: [ubuntu-latest, windows-latest] 81 | node-version: [12.x] 82 | 83 | steps: 84 | - name: Use Node.js ${{ matrix.node-version }} 85 | uses: actions/setup-node@v1 86 | with: 87 | node-version: ${{ matrix.node-version }} 88 | - uses: actions/checkout@v2 89 | with: 90 | fetch-depth: 3 91 | - name: Get origin master 92 | if: github.event_name == 'pull_request' 93 | run: | 94 | git fetch --no-tags --prune --depth=1 origin +refs/heads/master:refs/remotes/origin/master 95 | - uses: actions/cache@v1 96 | id: workspace-cache 97 | with: 98 | path: node_modules 99 | key: ${{ runner.os }}-${{ matrix.node-version }}-workspace-${{ hashFiles('**/yarn.lock') }} 100 | restore-keys: | 101 | ${{ runner.os }}-${{ matrix.node-version }}-workspace- 102 | - name: Run On Master (Linux) 103 | if: github.event_name == 'push' && runner.os == 'Linux' 104 | run: yarn affected:build --base=$GITHUB_REF~1 --head=$GITHUB_SHA --parallel 105 | - name: Run On Master (Windows) 106 | if: github.event_name == 'push' && runner.os == 'Windows' 107 | run: yarn affected:build --base=${env:GITHUB_REF}~1 --head=${env:GITHUB_SHA} --parallel 108 | - name: Run on PR 109 | if: github.event_name == 'pull_request' 110 | run: yarn affected:build --base=origin/master --head=HEAD --parallel 111 | 112 | test: 113 | runs-on: ${{ matrix.os }} 114 | 115 | needs: [setup] 116 | 117 | strategy: 118 | matrix: 119 | os: [ubuntu-latest, windows-latest] 120 | node-version: [12.x] 121 | 122 | steps: 123 | - name: Use Node.js ${{ matrix.node-version }} 124 | uses: actions/setup-node@v1 125 | with: 126 | node-version: ${{ matrix.node-version }} 127 | - uses: actions/checkout@v2 128 | with: 129 | fetch-depth: 3 130 | - name: Get origin master 131 | if: github.event_name == 'pull_request' 132 | run: | 133 | git fetch --no-tags --prune --depth=1 origin +refs/heads/master:refs/remotes/origin/master 134 | - uses: actions/cache@v1 135 | id: workspace-cache 136 | with: 137 | path: node_modules 138 | key: ${{ runner.os }}-${{ matrix.node-version }}-workspace-${{ hashFiles('**/yarn.lock') }} 139 | restore-keys: | 140 | ${{ runner.os }}-${{ matrix.node-version }}-workspace- 141 | - name: Run On Master (Linux) 142 | if: github.event_name == 'push' && runner.os == 'Linux' 143 | run: yarn affected:test --base=$GITHUB_REF~1 --head=$GITHUB_SHA --parallel 144 | - name: Run On Master (Windows) 145 | if: github.event_name == 'push' && runner.os == 'Windows' 146 | run: yarn affected:test --base=${env:GITHUB_REF}~1 --head=${env:GITHUB_SHA} --parallel 147 | - name: Run on PR 148 | if: github.event_name == 'pull_request' 149 | run: yarn affected:test --base=origin/master --head=HEAD --parallel 150 | -------------------------------------------------------------------------------- /packages/docs/docs/spectator-usage.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: spectator-usage 3 | title: nest-spectator 4 | --- 5 | 6 | > Auto-mocking for Nestjs providers 7 | 8 | ### 🏠 [Homepage](https://www.npmjs.com/package/nest-spectator) 9 | 10 | ## Author 11 | 12 | 👤 **Jay Bell ** 13 | 14 | ## Usage 15 | 16 | See `packages/nest-spectator/__tests__` for reference: 17 | 18 | Let's assume you we have the following 2 different Nestjs services: 19 | 20 | ``` 21 | @Injectable() 22 | class PrimaryService { 23 | constructor(secondaryService: SecondaryService) { 24 | } 25 | 26 | testFunction(): string { 27 | return 'test'; 28 | } 29 | } 30 | ``` 31 | 32 | and 33 | 34 | ``` 35 | @Injectable() 36 | class SecondaryService { 37 | } 38 | ``` 39 | 40 | and this controller: 41 | 42 | ``` 43 | @Controller() 44 | class PrimaryController { 45 | constructor(primaryService: PrimaryService) { 46 | } 47 | } 48 | ``` 49 | 50 | Currently we have something like: 51 | 52 | ``` 53 | import { Test } from '@nestjs/testing'; 54 | import { PrimaryController } from './primary.controller'; 55 | import { PrimaryService } from './primary.service'; 56 | import { SecondaryService } from './secondary.service'; 57 | 58 | describe('PrimaryController', () => { 59 | let primaryController: PrimaryController; 60 | let primaryService: PrimaryService; 61 | 62 | beforeEach(async () => { 63 | const module = await Test.createTestingModule({ 64 | controllers: [PrimaryController], 65 | providers: [PrimaryService, SecondaryService], 66 | }).compile(); 67 | 68 | primaryService = module.get(PrimaryService); 69 | primaryController = module.get(PrimaryController); 70 | }); 71 | }); 72 | ``` 73 | 74 | Which is fine for small projects but as your code base grows you could have many injections in your classes constructor that you are testing. 75 | Assuming you plan on creating mocks for the services injected into `PrimaryController` classes or you want to spy on the classes methods of `PrimaryService` your code can start to grow more and more for each time you have a new spec file. 76 | 77 | If you do not mock our your services and instead just want to spy on them, your spec files will grow very large because each service you provider in the `Test.createTestingModule` will need to have all of it's services injected as well. 78 | 79 | You can see the effect of this in the sample above, `SecondaryService` is injected because `PrimaryService` injects it which in turn is injected into `PrimaryController` 80 | 81 | If you are wanting to create mocks for each of these services injected in your class being tested then it would look something like this: 82 | 83 | ``` 84 | import { Test } from '@nestjs/testing'; 85 | import { PrimaryController } from './primary.controller'; 86 | import { PrimaryService } from './primary.service'; 87 | import { SecondaryService } from './secondary.service'; 88 | 89 | const mockPrimaryService = { 90 | testFunction: () => {} 91 | } 92 | 93 | class MockPrimaryService { 94 | testFunction(): void { 95 | } 96 | } 97 | 98 | describe('PrimaryController', () => { 99 | let primaryController: PrimaryController; 100 | let primaryService: PrimaryService; 101 | 102 | beforeEach(async () => { 103 | const module = await Test.createTestingModule({ 104 | controllers: [PrimaryController], 105 | providers: [ 106 | {provide: PrimaryService, useValue: mockPrimaryService} 107 | // OR 108 | {provide: PrimaryService, useClass: MockPrimaryService} 109 | ], 110 | }).compile(); 111 | 112 | primaryService = module.get(PrimaryService); 113 | primaryController = module.get(PrimaryController); 114 | }); 115 | }); 116 | ``` 117 | 118 | Now you will have to maintain these mock objects for each of your services and ensure you provide over your implementation services in each of your spec files. 119 | 120 | This is where `nest-spectator` comes in, inspired by [@ngneat/spectator](https://github.com/ngneat/spectator) for Angular, `nest-spectator` creates a layer on top of the `@nestjs/testing` 121 | `Test.creatingTestingModule` to provide the functionality to auto mock your services so that your module instantiation in tests turns into: 122 | 123 | ``` 124 | beforeEach(async () => { 125 | module = await createTestingModuleFactory( 126 | { 127 | imports: [], 128 | controllers: [PrimaryController], 129 | providers: [PrimaryService], 130 | mocks: [PrimaryService] 131 | }, 132 | ).compile(); 133 | }); 134 | ``` 135 | 136 | The `createTestingModuleFactory` accepts all the same values as the `Test.createTestingModule` function except that there is an option property `mocks?: Array>` 137 | that will accept the providers you want to auto mock. This function will return the same value (`TestingModuleBuilder`) as `Test.createTestingModule` will which means we just call 138 | `.compile()` on it after to get our testing module. 139 | 140 | If we need access to our services provided to this testing module we get them the same way as we would before since we just have a `TestingModule`. 141 | 142 | ``` 143 | const primaryService = module.get(PrimaryService); 144 | ``` 145 | 146 | If `PrimaryService` was included in the `mocks` array during test module instantiation than it will be an object that mirrors the structure of your class as if it was provided normally except 147 | that the methods, getters and setters will all be `jest` Spys themselves. 148 | 149 | When providing `PrimaryService` in the mocks array and using `module.get(T)` to get the instance of the provider it will return `SpyObject` instead of just `PrimaryService`. 150 | 151 | This package is still in alpha so there way be unintended side effects or gaps in the logic. If there is anything you would like to see include please feel free to open an issue. 152 | -------------------------------------------------------------------------------- /workspace.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "projects": { 4 | "playground": { 5 | "root": "packages/playground", 6 | "sourceRoot": "packages/playground/src", 7 | "projectType": "application", 8 | "prefix": "playground", 9 | "schematics": {}, 10 | "architect": { 11 | "build": { 12 | "builder": "@nrwl/node:build", 13 | "options": { 14 | "outputPath": "dist/packages/playground", 15 | "main": "packages/playground/src/main.ts", 16 | "tsConfig": "packages/playground/tsconfig.app.json", 17 | "assets": ["packages/playground/src/assets"] 18 | }, 19 | "configurations": { 20 | "production": { 21 | "optimization": true, 22 | "extractLicenses": true, 23 | "inspect": false, 24 | "fileReplacements": [ 25 | { 26 | "replace": "packages/playground/src/environments/environment.ts", 27 | "with": "packages/playground/src/environments/environment.prod.ts" 28 | } 29 | ] 30 | } 31 | }, 32 | "outputs": ["{options.outputPath}"] 33 | }, 34 | "serve": { 35 | "builder": "@nrwl/node:execute", 36 | "options": { 37 | "buildTarget": "playground:build" 38 | } 39 | }, 40 | "lint": { 41 | "builder": "@nrwl/linter:eslint", 42 | "options": { 43 | "lintFilePatterns": [ 44 | "packages/playground/**/*.ts", 45 | "packages/playground/**/*.spec.ts", 46 | "packages/playground/**/*.d.ts" 47 | ] 48 | } 49 | }, 50 | "test": { 51 | "builder": "@nrwl/jest:jest", 52 | "options": { 53 | "jestConfig": "packages/playground/jest.config.js", 54 | "passWithNoTests": true 55 | }, 56 | "outputs": ["coverage/packages/playground"] 57 | } 58 | } 59 | }, 60 | "in-memory-db": { 61 | "root": "packages/in-memory-db", 62 | "sourceRoot": "packages/in-memory-db/src", 63 | "projectType": "library", 64 | "schematics": {}, 65 | "architect": { 66 | "lint": { 67 | "builder": "@nrwl/linter:eslint", 68 | "options": { 69 | "lintFilePatterns": [ 70 | "packages/in-memory-db/**/*.ts", 71 | "packages/in-memory-db/**/*.spec.ts", 72 | "packages/in-memory-db/**/*.spec.tsx", 73 | "packages/in-memory-db/**/*.spec.js", 74 | "packages/in-memory-db/**/*.spec.jsx", 75 | "packages/in-memory-db/**/*.d.ts" 76 | ] 77 | } 78 | }, 79 | "test": { 80 | "builder": "@nrwl/jest:jest", 81 | "options": { 82 | "jestConfig": "packages/in-memory-db/jest.config.js", 83 | "passWithNoTests": true 84 | }, 85 | "outputs": ["coverage/packages/in-memory-db"] 86 | }, 87 | "build": { 88 | "builder": "@nrwl/node:package", 89 | "options": { 90 | "outputPath": "dist/packages/in-memory-db", 91 | "tsConfig": "packages/in-memory-db/tsconfig.lib.json", 92 | "packageJson": "packages/in-memory-db/package.json", 93 | "main": "packages/in-memory-db/src/index.ts", 94 | "assets": [ 95 | "packages/in-memory-db/*.md", 96 | { 97 | "glob": "**/*.json", 98 | "input": "packages/in-memory-db/src/schematics", 99 | "output": "src/schematics" 100 | } 101 | ] 102 | }, 103 | "outputs": ["{options.outputPath}"] 104 | } 105 | } 106 | }, 107 | "playground-e2e": { 108 | "root": "packages/playground-e2e", 109 | "sourceRoot": "packages/playground-e2e/src", 110 | "projectType": "application", 111 | "prefix": "playground-e2e", 112 | "schematics": {}, 113 | "architect": { 114 | "lint": { 115 | "builder": "@nrwl/linter:eslint", 116 | "options": { 117 | "lintFilePatterns": [ 118 | "packages/playground-e2e/**/*.spec.ts", 119 | "packages/playground-e2e/**/*.d.ts" 120 | ] 121 | } 122 | }, 123 | "e2e": { 124 | "builder": "@nrwl/jest:jest", 125 | "options": { 126 | "jestConfig": "packages/playground-e2e/jest.config.js", 127 | "passWithNoTests": true 128 | }, 129 | "outputs": ["coverage/packages/playground-e2e"] 130 | } 131 | } 132 | }, 133 | "spectator": { 134 | "root": "packages/spectator", 135 | "sourceRoot": "packages/spectator/src", 136 | "projectType": "library", 137 | "schematics": {}, 138 | "architect": { 139 | "lint": { 140 | "builder": "@nrwl/linter:eslint", 141 | "options": { 142 | "lintFilePatterns": [ 143 | "packages/spectator/**/*.ts", 144 | "packages/spectator/**/*.spec.ts", 145 | "packages/spectator/**/*.spec.tsx", 146 | "packages/spectator/**/*.spec.js", 147 | "packages/spectator/**/*.spec.jsx", 148 | "packages/spectator/**/*.d.ts" 149 | ] 150 | } 151 | }, 152 | "test": { 153 | "builder": "@nrwl/jest:jest", 154 | "options": { 155 | "jestConfig": "packages/spectator/jest.config.js", 156 | "passWithNoTests": true 157 | }, 158 | "outputs": ["coverage/packages/spectator"] 159 | }, 160 | "build": { 161 | "builder": "@nrwl/node:package", 162 | "options": { 163 | "outputPath": "dist/packages/spectator", 164 | "tsConfig": "packages/spectator/tsconfig.lib.json", 165 | "packageJson": "packages/spectator/package.json", 166 | "main": "packages/spectator/src/index.ts", 167 | "assets": ["packages/spectator/*.md"] 168 | }, 169 | "outputs": ["{options.outputPath}"] 170 | } 171 | } 172 | }, 173 | "docs": { 174 | "projectType": "application", 175 | "root": "packages/docs", 176 | "sourceRoot": "packages/docs/src", 177 | "architect": { 178 | "build": { 179 | "builder": "@nx-plus/docusaurus:browser", 180 | "options": { 181 | "outputPath": "dist/packages/docs" 182 | } 183 | }, 184 | "serve": { 185 | "builder": "@nx-plus/docusaurus:dev-server", 186 | "options": { 187 | "port": 3000 188 | } 189 | } 190 | } 191 | } 192 | }, 193 | "cli": { 194 | "defaultCollection": "@nrwl/nest" 195 | }, 196 | "defaultProject": "playground" 197 | } 198 | -------------------------------------------------------------------------------- /packages/playground-e2e/src/app.e2e.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import * as request from 'supertest'; 3 | import { AppModule, User } from '../../playground/src'; 4 | import { INestApplication } from '@nestjs/common'; 5 | 6 | describe('AppController (e2e)', () => { 7 | let app: INestApplication; 8 | 9 | beforeAll(async () => { 10 | const moduleFixture: TestingModule = await Test.createTestingModule({ 11 | imports: [AppModule], 12 | }).compile(); 13 | 14 | app = moduleFixture.createNestApplication(); 15 | await app.init(); 16 | }); 17 | 18 | describe('Verifies their are no records on creation', () => { 19 | test('/api/users (GET) - Get All Users = []', () => { 20 | return request(app.getHttpServer()) 21 | .get('/api/users') 22 | .expect(200) 23 | .expect([]); 24 | }); 25 | test('/api/user/:id (GET) Get User 1 = {}', () => { 26 | return request(app.getHttpServer()) 27 | .get('/api/user/1') 28 | .expect(200) 29 | .expect({}); 30 | }); 31 | }); 32 | 33 | describe('Create, Update, Read and Delete User', () => { 34 | const user: User = { id: '1', firstName: 'John', lastName: 'Doe' }; 35 | 36 | test('/api/user (POST) - Create New User', () => { 37 | return request(app.getHttpServer()) 38 | .post('/api/user') 39 | .send(user) 40 | .expect(201) 41 | .expect(user); 42 | }); 43 | test('/api/user/1 (PUT) - Update User 1', () => { 44 | user.firstName = 'Jane'; 45 | return request(app.getHttpServer()) 46 | .put('/api/user/1') 47 | .send(user) 48 | .expect(200) 49 | .expect({}); 50 | }); 51 | test('/api/user/1 (GET) - Get User 1', () => { 52 | return request(app.getHttpServer()) 53 | .get('/api/user/1') 54 | .expect(200) 55 | .expect(user); 56 | }); 57 | test('/api/user/1 (DELETE) - Delete User 1', () => { 58 | return request(app.getHttpServer()) 59 | .delete('/api/user/1') 60 | .expect(200) 61 | .expect({}); 62 | }); 63 | test('/api/users (GET) - Get All Users = []', () => { 64 | return request(app.getHttpServer()) 65 | .get('/api/users') 66 | .expect(200) 67 | .expect([]); 68 | }); 69 | }); 70 | 71 | describe('Create, Update, Read, Delete Users Asyncronously', () => { 72 | const user: User = { id: '1', firstName: 'John', lastName: 'Doe' }; 73 | 74 | test('/api/user/async (POST) - Create New User Async', () => { 75 | return request(app.getHttpServer()) 76 | .post('/api/user/async') 77 | .send(user) 78 | .expect(201) 79 | .expect(user); 80 | }); 81 | test('/api/user/1/async (PUT) - Updates User 1 Async', () => { 82 | user.firstName = 'Jane'; 83 | return request(app.getHttpServer()) 84 | .put('/api/user/1/async') 85 | .send(user) 86 | .expect(200) 87 | .expect({}); 88 | }); 89 | test('/api/user/1/async (GET) - Gets Updated User 1 Async', () => { 90 | return request(app.getHttpServer()) 91 | .get('/api/user/1/async') 92 | .expect(200) 93 | .expect(user); 94 | }); 95 | test('/api/user/1/async (DELETE) - Deletes User 1 Async', () => { 96 | return request(app.getHttpServer()) 97 | .delete('/api/user/1/async') 98 | .expect(200) 99 | .expect({}); 100 | }); 101 | }); 102 | 103 | describe('Create, Update Read & Delete 3 Users & Query By First & Last Name', () => { 104 | const user1: User = { id: '1', firstName: 'John', lastName: 'Doe' }; 105 | const user2: User = { id: '2', firstName: 'Jane', lastName: 'Doe' }; 106 | const user3: User = { id: '3', firstName: 'Joe', lastName: 'Shmoe' }; 107 | 108 | test('/api/users (POST) - Create 3 Users', () => { 109 | return request(app.getHttpServer()) 110 | .post('/api/users') 111 | .send([user1, user2, user3]) 112 | .expect(201) 113 | .expect([user1, user2, user3]); 114 | }); 115 | test('/api/users/firstName/Joe (GET) - Gets Users by First Name Joe', () => { 116 | return request(app.getHttpServer()) 117 | .get('/api/users/firstName/Joe') 118 | .expect(200) 119 | .expect([user3]); 120 | }); 121 | test('/api/users/lastName/Doe (GET) - Gets Users by Last Name Doe', () => { 122 | return request(app.getHttpServer()) 123 | .get('/api/users/lastName/Doe') 124 | .expect(200) 125 | .expect([user1, user2]); 126 | }); 127 | test('/api/users (PUT) - Update Users 1 and 2', () => { 128 | user1.lastName = 'Buck'; 129 | user2.lastName = 'Buck'; 130 | 131 | return request(app.getHttpServer()) 132 | .put('/api/users') 133 | .send([user1, user2]) 134 | .expect(200) 135 | .expect({}); 136 | }); 137 | test('/api/users (GET) - Gets All 3 Users', () => { 138 | return request(app.getHttpServer()) 139 | .get('/api/users') 140 | .expect(200) 141 | .expect([user1, user2, user3]); 142 | }); 143 | test('/api/users (DELETE) - Deletes All 3 Users', () => { 144 | return request(app.getHttpServer()) 145 | .delete('/api/users') 146 | .send([1, 2, 3]) 147 | .expect(200) 148 | .expect({}); 149 | }); 150 | test('/api/users (GET) - Gets All Users = []', () => { 151 | return request(app.getHttpServer()) 152 | .get('/api/users') 153 | .expect(200) 154 | .expect([]); 155 | }); 156 | }); 157 | 158 | describe('Create, Update Read & Delete 3 Users & Query By First & Last Name Asyncronously', () => { 159 | const user1: User = { id: '1', firstName: 'John', lastName: 'Doe' }; 160 | const user2: User = { id: '2', firstName: 'Jane', lastName: 'Doe' }; 161 | const user3: User = { id: '3', firstName: 'Joe', lastName: 'Shmoe' }; 162 | 163 | test('/api/users/async (POST) - Create 3 Users Asyncronously', () => { 164 | return request(app.getHttpServer()) 165 | .post('/api/users/async') 166 | .send([user1, user2, user3]) 167 | .expect(201) 168 | .expect([user1, user2, user3]); 169 | }); 170 | test('/api/users/firstName/Joe/async (GET) - Gets Users by First Name Joe Asyncronously', () => { 171 | return request(app.getHttpServer()) 172 | .get('/api/users/firstName/Joe/async') 173 | .expect(200) 174 | .expect([user3]); 175 | }); 176 | test('/api/users/lastName/Doe/async (GET) - Gets Users by Last Name Doe Asyncronously', () => { 177 | return request(app.getHttpServer()) 178 | .get('/api/users/lastName/Doe/async') 179 | .expect(200) 180 | .expect([user1, user2]); 181 | }); 182 | test('/api/users/async (PUT) - Updates Users 1 and 2 Async', () => { 183 | return request(app.getHttpServer()) 184 | .put('/api/users/async') 185 | .send([user1, user2]) 186 | .expect(200) 187 | .expect({}); 188 | }); 189 | test('/api/users/async (GET) - Gets All 3 Users Async', () => { 190 | return request(app.getHttpServer()) 191 | .get('/api/users/async') 192 | .expect(200) 193 | .expect([user1, user2, user3]); 194 | }); 195 | test('/api/users/async (DELETE) - Deletes All 3 Users Async', () => { 196 | return request(app.getHttpServer()) 197 | .delete('/api/users/async') 198 | .send([1, 2, 3]) 199 | .expect(200) 200 | .expect({}); 201 | }); 202 | test('/api/users/async (GET) - Gets All Users Async = []', () => { 203 | return request(app.getHttpServer()) 204 | .get('/api/users/async') 205 | .expect(200) 206 | .expect([]); 207 | }); 208 | }); 209 | 210 | afterAll(async () => { 211 | await app.close(); 212 | }); 213 | }); 214 | -------------------------------------------------------------------------------- /packages/docs/src/pages/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import clsx from 'clsx'; 3 | import Layout from '@theme/Layout'; 4 | import Link from '@docusaurus/Link'; 5 | import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; 6 | import useBaseUrl from '@docusaurus/useBaseUrl'; 7 | import styles from './styles.module.css'; 8 | 9 | const maintainers = [ 10 | { 11 | name: 'Wes Grimes', 12 | githubHandle: 'wesgrimes', 13 | url: 'https://wesleygrimes.com', 14 | imgUrl: 'https://avatars0.githubusercontent.com/u/324308?v=4', 15 | tasks: [ 16 | { 17 | icon: '🚇', 18 | description: 'Infrastructure (Hosting, Build-Tools, etc)' 19 | }, 20 | { 21 | icon: '⚠️', 22 | description: 'Tests' 23 | }, 24 | { 25 | icon: '💻', 26 | description: 'Code' 27 | } 28 | ] 29 | }, 30 | { 31 | name: 'Jay Bell', 32 | githubHandle: 'yharaskrik', 33 | url: 'https://github.com/yharaskrik', 34 | imgUrl: 'https://avatars3.githubusercontent.com/u/9469090?s=460&u=cdb912283b06f43b36da0137e61d66a48f9f7e85&v=4', 35 | tasks: [ 36 | { 37 | icon: '⚠️', 38 | description: 'Tests' 39 | }, 40 | { 41 | icon: '💻', 42 | description: 'Code' 43 | } 44 | ] 45 | }, 46 | { 47 | name: 'Dominik Pieper', 48 | githubHandle: 'DominikPieper', 49 | url: 'https://github.com/DominikPieper', 50 | imgUrl: 'https://avatars3.githubusercontent.com/u/77470?s=460&u=dcb757adc603e0d4caebb02182be9674299e0de0&v=4', 51 | tasks: [ 52 | { 53 | icon: '🚇', 54 | description: 'Infrastructure (Hosting, Build-Tools, etc)' 55 | }, 56 | { 57 | icon: '⚠️', 58 | description: 'Tests' 59 | }, 60 | { 61 | icon: '💻', 62 | description: 'Code' 63 | } 64 | ] 65 | } 66 | ]; 67 | 68 | const contributors = [ 69 | { 70 | name: 'Chris Whited', 71 | githubHandle: 'cmwhited', 72 | url: 'https://github.com/cmwhited', 73 | imgUrl: 'https://avatars0.githubusercontent.com/u/18075124?v=4', 74 | tasks: [ 75 | { 76 | icon: '🚇', 77 | description: 'Infrastructure (Hosting, Build-Tools, etc)' 78 | }, 79 | { 80 | icon: '⚠️', 81 | description: 'Tests' 82 | }, 83 | { 84 | icon: '💻', 85 | description: 'Code' 86 | } 87 | ] 88 | }, 89 | { 90 | name: 'Wes Copeland', 91 | githubHandle: 'wescopeland', 92 | url: 'https://github.com/wescopeland', 93 | imgUrl: 'https://avatars0.githubusercontent.com/u/3984985?v=4', 94 | tasks: [ 95 | { 96 | icon: '⚠️', 97 | description: 'Tests' 98 | }, 99 | { 100 | icon: '💻', 101 | description: 'Code' 102 | } 103 | ] 104 | }, 105 | { 106 | name: 'Jordan', 107 | githubHandle: 'jordanpowell88', 108 | url: 'http://hirejordanpowell.com', 109 | imgUrl: 'https://avatars0.githubusercontent.com/u/3605268?v=4', 110 | tasks: [ 111 | { 112 | icon: '⚠️', 113 | description: 'Tests' 114 | }, 115 | { 116 | icon: '💻', 117 | description: 'Code' 118 | } 119 | ] 120 | }, 121 | { 122 | name: 'Santosh Yadav', 123 | githubHandle: 'santoshyadav198613', 124 | url: 'https://www.santoshyadav.dev', 125 | imgUrl: 'https://avatars3.githubusercontent.com/u/11923975?v=4', 126 | tasks: [ 127 | { 128 | icon: '⚠️', 129 | description: 'Tests' 130 | }, 131 | { 132 | icon: '💻', 133 | description: 'Code' 134 | } 135 | ] 136 | }, 137 | { 138 | name: 'Itay Oded', 139 | githubHandle: 'itayod', 140 | url: 'https://github.com/itayod', 141 | imgUrl: 'https://avatars2.githubusercontent.com/u/6719615?v=4', 142 | tasks: [ 143 | { 144 | icon: '⚠️', 145 | description: 'Tests' 146 | }, 147 | { 148 | icon: '💻', 149 | description: 'Code' 150 | } 151 | ] 152 | } 153 | ]; 154 | 155 | const features = [ 156 | { 157 | title: <>Easy to Use, 158 | imageUrl: 'img/undraw_docusaurus_mountain.svg', 159 | description: ( 160 | <> 161 | Docusaurus was designed from the ground up to be easily installed and 162 | used to get your website up and running quickly. 163 | 164 | ), 165 | }, 166 | { 167 | title: <>Focus on What Matters, 168 | imageUrl: 'img/undraw_docusaurus_tree.svg', 169 | description: ( 170 | <> 171 | Docusaurus lets you focus on your docs, and we'll do the chores. Go 172 | ahead and move your docs into the docs directory. 173 | 174 | ), 175 | }, 176 | { 177 | title: <>Powered by React, 178 | imageUrl: 'img/undraw_docusaurus_react.svg', 179 | description: ( 180 | <> 181 | Extend or customize your website layout by reusing React. Docusaurus can 182 | be extended while reusing the same header and footer. 183 | 184 | ), 185 | }, 186 | ]; 187 | 188 | function Feature({ imageUrl, title, description }) { 189 | const imgUrl = useBaseUrl(imageUrl); 190 | return ( 191 |
192 | {imgUrl && ( 193 |
194 | {title} 195 |
196 | )} 197 |

{title}

198 |

{description}

199 |
200 | ); 201 | } 202 | 203 | function DeveloperCard({ name, imgUrl, tasks, githubHandle, url }) { 204 | return ( 205 |
206 |
207 |
208 |
209 | {imgUrl && ( 210 | {name} 211 | )} 212 |

{name}

213 |
214 |
215 | {tasks.map((props, idx) => ( 216 | {props.icon} 217 | ))} 218 |
219 |
220 |
221 |
222 | ); 223 | } 224 | 225 | function Home() { 226 | const context = useDocusaurusContext(); 227 | const { siteConfig = {} } = context; 228 | return ( 229 | 233 |
234 |
235 |

{siteConfig.title}

236 |

{siteConfig.tagline}

237 |
238 | 245 | Get Started 246 | 247 |
248 |
249 |
250 |
251 | {features && features.length > 0 && ( 252 |
253 |
254 |
255 | {features.map((props, idx) => ( 256 | 257 | ))} 258 |
259 |
260 |
261 | )} 262 | 263 |
264 |

Maintainers

265 |
266 | {maintainers && maintainers.length > 0 && ( 267 |
268 |
269 |
270 | {maintainers.map((props, idx) => ( 271 | 272 | ))} 273 |
274 |
275 |
276 | )} 277 | 278 |
279 |

Contributors

280 |

Thanks goes to these wonderful people (emoji key):

281 |
282 | {contributors && contributors.length > 0 && ( 283 |
284 |
285 |
286 | {contributors.map((props, idx) => ( 287 | 288 | ))} 289 |
290 |
291 |
292 | )} 293 | 294 |
295 |
296 | ); 297 | } 298 | 299 | export default Home; 300 | -------------------------------------------------------------------------------- /packages/in-memory-db/src/services/in-memory-db.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Optional } from '@nestjs/common'; 2 | 3 | import { InMemoryDBConfig, InMemoryDBEntity } from '../interfaces'; 4 | import { Observable, of } from 'rxjs'; 5 | 6 | import { v4 as uuid } from 'uuid'; 7 | 8 | @Injectable() 9 | export class InMemoryDBService { 10 | private recordMap: { [id: string]: T } = {}; 11 | 12 | constructor(@Optional() private readonly config: InMemoryDBConfig) {} 13 | 14 | /** 15 | * Given the array of records of type `T`, reduce the array into a dictionary object of 16 | * type `{ [id: string]: T }`. Set the value of the in-memory data store 17 | * to this reduced input array. 18 | * Example: 19 | * 20 | * - input array 21 | * ```json5 22 | * [ 23 | * { 24 | * "id": "random-uuid", 25 | * "prop": "test1" 26 | * }, 27 | * { 28 | * "id": "another-random-uuid", 29 | * "prop": "test2" 30 | * } 31 | * ] 32 | * ``` 33 | * - becomes 34 | * ```json5 35 | * { 36 | * "random-uuid": { "id": "random-uuid", "prop": "test1" }, 37 | * "another-random-uuid": { "id": "another-random-uuid", "prop": "test2" } 38 | * } 39 | * ``` 40 | * @param records the array of records of type T 41 | */ 42 | set records(records: T[]) { 43 | if (!records || records.length === 0) { 44 | this.recordMap = {}; 45 | } 46 | this.recordMap = records.reduce( 47 | (previous: { [id: string]: T }, current: T) => { 48 | return { 49 | ...previous, 50 | [current.id]: current, 51 | }; 52 | }, 53 | this.recordMap, 54 | ); 55 | } 56 | get records(): T[] { 57 | return Object.keys(this.recordMap).map((key) => this.recordMap[key]); 58 | } 59 | 60 | /** 61 | * Add the supplied `record` partial to the in-memory data store of records. 62 | * Get the `id` of the record by getting the next available `id` value. 63 | * Returns the updated record with the newly generated `id`. 64 | * @param record the partial record of type `T` to create 65 | */ 66 | public create(record: Partial, getNextId: () => string = () => uuid()): T { 67 | const id = record.id || getNextId(); 68 | const newRecord: T = { ...record, id } as T; 69 | this.recordMap = { 70 | ...this.recordMap, 71 | [newRecord.id]: newRecord, 72 | }; 73 | return newRecord; 74 | } 75 | 76 | /** 77 | * Add the supplied `record` partial to the in-memroy data store of records asyncronously. 78 | * Get the `id` of the record by getting the next available `id` value. 79 | * Returns the updated record with the newly generated `id` as an observable. 80 | * @param record the partial record of type `T` to create 81 | */ 82 | public createAsync(record: Partial): Observable { 83 | const result$ = of(this.create(record)); 84 | return result$; 85 | } 86 | 87 | /** 88 | * Add the supplied `records` partials array to in-memory data store of records. 89 | * Get the `id` of the record by getting the next available `id` value. 90 | * Returns a sequential array of the records with the newly generated `ids`. 91 | * @param records an array of partial records of type `T` to create 92 | */ 93 | public createMany( 94 | records: Array>, 95 | getNextId: () => string = () => uuid(), 96 | ): T[] { 97 | return records.map((record) => this.create(record, getNextId)); 98 | } 99 | 100 | /** 101 | * Add the supplied `records` partials array to in-memory data store of records. 102 | * Get the `id` of the record by getting the next available `id` value. 103 | * Returns a sequential array of the records with the newly generated `ids` as an Observable. 104 | * @param records an array of partial records of type `T` to create 105 | */ 106 | public createManyAsync( 107 | records: Array>, 108 | getNextId: () => string = () => uuid(), 109 | ): Observable { 110 | const result$ = of(this.createMany(records, getNextId)); 111 | return result$; 112 | } 113 | 114 | /** 115 | * Update a record in the in-memory data store of type `T` using the supplied record. 116 | * @param record the record of type `T` to update 117 | */ 118 | public update(record: T): void { 119 | this.recordMap = { 120 | ...this.recordMap, 121 | [record.id]: { ...record }, 122 | }; 123 | } 124 | 125 | /** 126 | * Update a record in the in-memory data store of type `T` using the supplied record asyncronously. 127 | * @param record the record of type `T` to update 128 | */ 129 | public updateAsync(record: T): Observable { 130 | this.update(record); 131 | const result$ = of(); 132 | return result$; 133 | } 134 | 135 | /** 136 | * Update records in the in-memory data store of type `T` using the supplied records. 137 | * @param records an array of records of type `T` to update 138 | */ 139 | public updateMany(records: T[]): void { 140 | for (const record of records) { 141 | this.update(record); 142 | } 143 | } 144 | 145 | /** 146 | * Update records in the in-memory data store of type `T` using the supplied records asyncronously. 147 | * @param records an array of records of type `T` to update 148 | */ 149 | public updateManyAsync(records: T[]): Observable { 150 | this.updateMany(records); 151 | const result$ = of(); 152 | return result$; 153 | } 154 | 155 | /** 156 | * Remove the record of type `T` from the in-memory data store using the supplied PK id. 157 | * @param id the PK id of the record 158 | */ 159 | public delete(id: string): void { 160 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 161 | const { [id]: removed, ...remainder } = this.recordMap; 162 | this.recordMap = { 163 | ...remainder, 164 | }; 165 | } 166 | 167 | /** 168 | * Remove the record of type `T` from the in-memory data store using the supplied PK id asyncronously. 169 | * @param id the PK id of the record 170 | */ 171 | public deleteAsync(id: string): Observable { 172 | this.delete(id); 173 | const result$ = of(); 174 | return result$; 175 | } 176 | 177 | /** 178 | * Remove the records of type `T` from the in-memory data store using the supplied PK ids. 179 | * @param ids the PK ids of the records 180 | */ 181 | public deleteMany(ids: string[]): void { 182 | for (const id of ids) { 183 | this.delete(id); 184 | } 185 | } 186 | 187 | /** 188 | * Remove the records of type `T` from the in-memory data store using the supplied PK ids asyncronously. 189 | * @param ids the PK ids of the records 190 | */ 191 | public deleteManyAsync(ids: string[]): Observable { 192 | this.deleteMany(ids); 193 | const result$ = of(); 194 | return result$; 195 | } 196 | 197 | /** 198 | * Get a single record of type `T` with the supplied id value. 199 | * @param id the PK id of the record 200 | */ 201 | public get(id: string): T { 202 | return this.recordMap[id]; 203 | } 204 | 205 | /** 206 | * Get a single record of type `T` with the supplied id value as an Observable; 207 | * @param id the PK id of the record 208 | */ 209 | public getAsync(id: string): Observable { 210 | const result$ = of(this.get(id)); 211 | return result$; 212 | } 213 | 214 | /** 215 | * Get records of type `T` with the supplied id values. 216 | * @param ids the PK ids of the records 217 | */ 218 | public getMany(ids: string[]): T[] { 219 | const records = ids 220 | .filter((id) => this.recordMap[id]) 221 | .map((id) => { 222 | return this.recordMap[id]; 223 | }); 224 | 225 | return records; 226 | } 227 | 228 | /** 229 | * Get records of type Observable `T` with the supplied id values 230 | * @param ids the PK ids of the records 231 | */ 232 | public getManyAsync(ids: string[]): Observable { 233 | const result$ = of(this.getMany(ids)); 234 | return result$; 235 | } 236 | 237 | /** 238 | * Return all of the records of type `T`. 239 | */ 240 | public getAll(): T[] { 241 | return this.records || []; 242 | } 243 | 244 | /** 245 | * Return all the records of type `T` as an Observable. 246 | */ 247 | public getAllAsync(): Observable { 248 | const result$ = of(this.getAll()); 249 | return result$; 250 | } 251 | 252 | /** 253 | * Return an array of records of type `T` filtered with the supplied predicate. 254 | * Example: 255 | * - given records: 256 | * ```json5 257 | * [ 258 | * { 259 | * "id": "random-uuid", 260 | * "prop": "test1" 261 | * }, 262 | * { 263 | * "id": "another-random-uuid", 264 | * "prop": "test2" 265 | * } 266 | * ] 267 | * ``` 268 | * - to find records with a `prop` value of `test1`: 269 | * ```ts 270 | * const records: T[] = service.query(record => record.prop === 'test1'); 271 | * ``` 272 | * @param predicate the filter predicate 273 | */ 274 | public query(predicate: (record: T) => boolean): T[] { 275 | return this.records.filter(predicate); 276 | } 277 | 278 | /** 279 | * Return an array of records of type `T` filtered with the supplied predicate as an Observable. 280 | * Example: 281 | * - given records: 282 | * ```json5 283 | * [ 284 | * { 285 | * "id": "random-uuid", 286 | * "prop": "test1" 287 | * }, 288 | * { 289 | * "id": "another-random-uuid", 290 | * "prop": "test2" 291 | * } 292 | * ] 293 | * ``` 294 | * - to find records with a `prop` value of `test1`: 295 | * ```ts 296 | * const records: Observable = service.queryAsync(record => record.prop === 'test1'); 297 | * ``` 298 | * @param predicate the filter predicate 299 | */ 300 | public queryAsync(predicate: (record: T) => boolean): Observable { 301 | const result$ = of(this.query(predicate)); 302 | return result$; 303 | } 304 | 305 | /** 306 | * Randomly generate at a set of records for the given amount and record factory. 307 | * Example: 308 | * ```typescript 309 | * service.seed((i) => { myProp: i}, 100); 310 | * ``` 311 | * 312 | * @param recordFactory a factory method to call when generating the random record. 313 | * @param amount the amount of records to generate, defaults to 10. 314 | */ 315 | public seed( 316 | recordFactory: (index: number) => Partial, 317 | amount = 10, 318 | getNextId: () => string = () => uuid(), 319 | ): void { 320 | amount = amount === null ? 10 : amount; 321 | 322 | const recordsToCreate = [...Array(amount).keys()].map((i) => 323 | recordFactory(i), 324 | ); 325 | 326 | this.createMany(recordsToCreate, getNextId); 327 | } 328 | } 329 | -------------------------------------------------------------------------------- /packages/spectator/README.md: -------------------------------------------------------------------------------- 1 |

Welcome to nest-spectator 👋

2 |

3 | 4 | Version 5 | 6 | 7 | License: MIT 8 | 9 |

10 | 11 | > Auto-mocking for Nestjs providers 12 | 13 | ### 🏠 [Homepage](https://www.npmjs.com/package/nest-spectator) 14 | 15 | ## Author 16 | 17 | 👤 **Jay Bell ** 18 | 19 | ## Usage 20 | 21 | See `packages/nest-spectator/__tests__` for reference: 22 | 23 | Let's assume you we have the following 2 different Nestjs services: 24 | 25 | ``` 26 | @Injectable() 27 | class PrimaryService { 28 | constructor(secondaryService: SecondaryService) { 29 | } 30 | 31 | testFunction(): string { 32 | return 'test'; 33 | } 34 | } 35 | ``` 36 | 37 | and 38 | 39 | ``` 40 | @Injectable() 41 | class SecondaryService { 42 | } 43 | ``` 44 | 45 | and this controller: 46 | 47 | ``` 48 | @Controller() 49 | class PrimaryController { 50 | constructor(primaryService: PrimaryService) { 51 | } 52 | } 53 | ``` 54 | 55 | Currently we have something like: 56 | 57 | ``` 58 | import { Test } from '@nestjs/testing'; 59 | import { PrimaryController } from './primary.controller'; 60 | import { PrimaryService } from './primary.service'; 61 | import { SecondaryService } from './secondary.service'; 62 | 63 | describe('PrimaryController', () => { 64 | let primaryController: PrimaryController; 65 | let primaryService: PrimaryService; 66 | 67 | beforeEach(async () => { 68 | const module = await Test.createTestingModule({ 69 | controllers: [PrimaryController], 70 | providers: [PrimaryService, SecondaryService], 71 | }).compile(); 72 | 73 | primaryService = module.get(PrimaryService); 74 | primaryController = module.get(PrimaryController); 75 | }); 76 | }); 77 | ``` 78 | 79 | Which is fine for small projects but as your code base grows you could have many injections in your classes constructor that you are testing. 80 | Assuming you plan on creating mocks for the services injected into `PrimaryController` classes or you want to spy on the classes methods of `PrimaryService` your code can start to grow more and more for each time you have a new spec file. 81 | 82 | If you do not mock our your services and instead just want to spy on them, your spec files will grow very large because each service you provider in the `Test.createTestingModule` will need to have all of it's services injected as well. 83 | 84 | You can see the effect of this in the sample above, `SecondaryService` is injected because `PrimaryService` injects it which in turn is injected into `PrimaryController` 85 | 86 | If you are wanting to create mocks for each of these services injected in your class being tested then it would look something like this: 87 | 88 | ``` 89 | import { Test } from '@nestjs/testing'; 90 | import { PrimaryController } from './primary.controller'; 91 | import { PrimaryService } from './primary.service'; 92 | import { SecondaryService } from './secondary.service'; 93 | 94 | const mockPrimaryService = { 95 | testFunction: () => {} 96 | } 97 | 98 | class MockPrimaryService { 99 | testFunction(): void { 100 | } 101 | } 102 | 103 | describe('PrimaryController', () => { 104 | let primaryController: PrimaryController; 105 | let primaryService: PrimaryService; 106 | 107 | beforeEach(async () => { 108 | const module = await Test.createTestingModule({ 109 | controllers: [PrimaryController], 110 | providers: [ 111 | {provide: PrimaryService, useValue: mockPrimaryService} 112 | // OR 113 | {provide: PrimaryService, useClass: MockPrimaryService} 114 | ], 115 | }).compile(); 116 | 117 | primaryService = module.get(PrimaryService); 118 | primaryController = module.get(PrimaryController); 119 | }); 120 | }); 121 | ``` 122 | 123 | Now you will have to maintain these mock objects for each of your services and ensure you provide over your implementation services in each of your spec files. 124 | 125 | This is where `nest-spectator` comes in, inspired by [@ngneat/spectator](https://github.com/ngneat/spectator) for Angular, `nest-spectator` creates a layer on top of the `@nestjs/testing` 126 | `Test.creatingTestingModule` to provide the functionality to auto mock your services so that your module instantiation in tests turns into: 127 | 128 | ``` 129 | beforeEach(async () => { 130 | module = await createTestingModuleFactory( 131 | { 132 | imports: [], 133 | controllers: [PrimaryController], 134 | providers: [PrimaryService], 135 | mocks: [PrimaryService] 136 | }, 137 | ).compile(); 138 | }); 139 | ``` 140 | 141 | The `createTestingModuleFactory` accepts all the same values as the `Test.createTestingModule` function except that there is an option property `mocks?: Array>` 142 | that will accept the providers you want to auto mock. This function will return the same value (`TestingModuleBuilder`) as `Test.createTestingModule` will which means we just call 143 | `.compile()` on it after to get our testing module. 144 | 145 | If we need access to our services provided to this testing module we get them the same way as we would before since we just have a `TestingModule`. 146 | 147 | ``` 148 | const primaryService = module.get(PrimaryService); 149 | ``` 150 | 151 | If `PrimaryService` was included in the `mocks` array during test module instantiation than it will be an object that mirrors the structure of your class as if it was provided normally except 152 | that the methods, getters and setters will all be `jest` Spys themselves. 153 | 154 | When providing `PrimaryService` in the mocks array and using `module.get(T)` to get the instance of the provider it will return `SpyObject` instead of just `PrimaryService`. 155 | 156 | This package is still in alpha so there way be unintended side effects or gaps in the logic. If there is anything you would like to see include please feel free to open an issue. 157 | 158 | ## 🤝 Contributing 159 | 160 | Contributions, issues and feature requests are welcome!
Feel free to check [issues page](https://github.com/yharaskrik/nest-testing/issues). You can also take a look at the [contributing guide](https://www.npmjs.com/package/nest-spectator/blob/master/CONTRIBUTING.md). 161 | 162 | ## Show your support 163 | 164 | Give a ⭐️ if this project helped you! 165 | 166 | 167 | ## Stay in touch 168 | 169 | - Author - [Wes Grimes](https://wesleygrimes.com) 170 | - Website - [https://github.com/nestjs-addons/in-memory-db](https://github.com/nestjs-addons/in-memory-db/) 171 | - Twitter - [@wesgrimes](https://twitter.com/wesgrimes) 172 | 173 | ## Maintainers ✨ 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 |
Wes Grimes
Wes Grimes

🚇 ⚠️ 💻
Jay Bell
Jay Bell

⚠️ 💻
183 | 184 | 185 | 186 | ## Contributors ✨ 187 | 188 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 |
Chris Whited
Chris Whited

🚇 ⚠️ 💻
Wes Copeland
Wes Copeland

💻 ⚠️
Jordan
Jordan

💻 ⚠️
Santosh Yadav
Santosh Yadav

💻 ⚠️
Itay Oded
Itay Oded

💻 ⚠️
201 | 202 | 203 | 204 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! 205 | 206 | ## License 207 | 208 | NestJS Addons is [MIT licensed](LICENSE). 209 | 210 | --- 211 | 212 | _This README was generated with ❤️ by [readme-md-generator](https://github.com/kefranabg/readme-md-generator)_ 213 | --------------------------------------------------------------------------------