有angular、nestjs经验的程序员一定会意识到依赖注入(dependency injection)带来的便利, 当然依赖注入不是什么新鲜的概念,并且也不是nodejs平台首创。
本文旨在介绍如果自己定义一个简单的注入帮助类。
实践步骤如下:
安装依赖
npm init --y
npm install typescript ts-node jasmine jasmine-core @types/jasmine lodash --save-dev
npm install karma karma-chrome-launcher karma-cli karma-coverage karma-jasmine karma-typescript --save
添加配置
tsconfig.json
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"lib": ["es2015", "es2016", "es2017", "dom"],
"declaration": true,
"outDir": "./lib",
"rootDir": "./src/",
"strictNullChecks": false,
"noImplicitThis": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"alwaysStrict": false,
"noFallthroughCasesInSwitch": true,
"baseUrl": "./src/",
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"downlevelIteration": true,
"strict": false,
"isolatedModules": true,
"noUncheckedIndexedAccess": true,
"esModuleInterop": true
},
"exclude": ["site", "lib"]
}
确保experimentalDecorators,emitDecoratorMetadata都有启用
jasmine.json
{
"spec_files": ["**/*[sS]pec.ts"]
}
karma.conf.js
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ["jasmine", "karma-typescript"],
files: [
'./src/unittest-demo/*.ts'
],
exclude: [
'karma.conf.js'
],
preprocessors: {
"./src/unittest-demo/*.ts": ["karma-typescript"] // *.tsx for React Jsx
},
reporters: ["progress", "karma-typescript"],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false,
concurrency: Infinity
})
}
注意./src/unittest-demo/*.ts 这个配置表示要对哪些ts文件产出coverage report
新建folder -> src/unittest-demo, 添加文件app.ts, app.spec.ts, inject.ts
inject.ts
import { find } from 'lodash';
export interface IContainerProvider {
useValue: any;
token: string;
}
export class Container {
providers: { [key: string]: any } = {};
public resolve(token: string) {
const matchedProvider = find(
this.providers,
(_provider, key) => key === token
);
if (matchedProvider) {
return matchedProvider;
} else {
throw new Error(`No provider found for ${token}!`);
}
}
public provide(details: IContainerProvider): void {
this.providers[details.token] = details.useValue;
}
}
export const container = new Container();
export function Injectable(token: string): Function {
return function(target: { new () }): void {
container.providers[token] = new target();
};
}
export function Inject(token: string) {
return function(target: any, key: string) {
Object.defineProperty(target, key, {
get: () => container.resolve(token),
enumerable: true,
configurable: true
});
};
}
app.ts
@Injectable('testService')
export class TestService {
public log(msg: string): void {
console.log(msg);
}
}
@Injectable('consumer')
export class Consumer {
@Inject('testService') private testService;
constructor() {
this.testService.log('Hey!');
}
}
container.resolve('consumer');
injectable标签会为该class新建一个实例,并加入到container的providers对象中,以供后续使用。
inject标签则是使用该实例,通过defineProperty方法修改对应属性的get方法,返回实例。
最后是单元测试部分
package.json -> scripts加入
"test": "ts-node node_modules/jasmine/bin/jasmine --config=jasmine.json",
"coverage": "karma start karma.conf.js"
app.spec.ts
import * as app from './app-inject';
import { container, Injectable, Inject } from './inject';
class mockService {
public log(msg: string): void {
console.log('from mock service');
}
}
describe('demo', function() {
container.provide({
token: 'testService',
useValue: new mockService()
})
it('unit-test', function() {
let result = app.Consumer;
expect(result).toBeDefined();
})
})
上述代码通过替换依赖的方式,使用mockService代替了原先的testService类。
最后npm run coverage, 成功后可以在项目根目录下看到coverage文件夹
进入coverage\Chrome 94.0.4606.61 (Windows 10)\html, 打开index.html就可以看到coverage report