簡體   English   中英

TypeScript 中的依賴注入

[英]Dependency injection in TypeScript

我正在研究使用 TypeScript 進行 TDD 的可能性。 如果我用 TypeScript 編寫測試,是否可以讓 import 語句為我的測試類返回模擬? 還是用純 JavaScript 編寫測試並自己處理注入 AMD 的唯一可行方法?

我開發了一個名為 InversifyJS 的 IoC 容器,它具有諸如上下文綁定之類的高級依賴注入功能。

您需要遵循3 個基本步驟才能使用它:

1.添加注釋

注釋 API 基於 Angular 2.0:

import { injectable, inject } from "inversify";

@injectable()
class Katana implements IKatana {
    public hit() {
        return "cut!";
    }
}

@injectable()
class Shuriken implements IShuriken {
    public throw() {
        return "hit!";
    }
}

@injectable()
class Ninja implements INinja {

    private _katana: IKatana;
    private _shuriken: IShuriken;

    public constructor(
        @inject("IKatana") katana: IKatana,
        @inject("IShuriken") shuriken: IShuriken
    ) {
        this._katana = katana;
        this._shuriken = shuriken;
    }

    public fight() { return this._katana.hit(); };
    public sneak() { return this._shuriken.throw(); };

}

2. 聲明綁定

綁定 API 基於 Ninject:

import { Kernel } from "inversify";

import { Ninja } from "./entities/ninja";
import { Katana } from "./entities/katana";
import { Shuriken} from "./entities/shuriken";

var kernel = new Kernel();
kernel.bind<INinja>("INinja").to(Ninja);
kernel.bind<IKatana>("IKatana").to(Katana);
kernel.bind<IShuriken>("IShuriken").to(Shuriken);

export default kernel;

3.解決依賴關系

解析 API 基於 Ninject:

import kernel = from "./inversify.config";

var ninja = kernel.get<INinja>("INinja");

expect(ninja.fight()).eql("cut!"); // true
expect(ninja.sneak()).eql("hit!"); // true

最新版本 (2.0.0) 支持許多用例:

  • 內核模塊
  • 內核中間件
  • 使用類、字符串文字或符號作為依賴標識符
  • 注入常​​量值
  • 注入類構造函數
  • 工廠注入
  • 汽車廠
  • 注入提供者(異步工廠)
  • 激活處理程序(用於注入代理)
  • 多次注射
  • 標記綁定
  • 自定義標簽裝飾器
  • 命名綁定
  • 上下文綁定
  • 友好的異常(例如循環依賴)

您可以在https://github.com/inversify/InversifyJS了解更多信息

我在 TypeScript 中使用infuse.js進行依賴注入。

參考 d.ts

/// <reference path="definition/infusejs/infusejs.d.ts"/>

在啟動時初始化您的注射器

this.injector = new infuse.Injector();  

映射依賴項

this.injector.mapClass( 'TodoController', TodoController );
this.injector.mapClass( 'TodoView', TodoView );
this.injector.mapClass( 'TodoModel', TodoModel, true );  // 'true' Map as singleton

注入依賴

export class TodoController
{
    static inject = ['TodoView', 'TodoModel'];

    constructor( todoView:TodoView, todoModel:TodoModel )
    {

    }
 }

它是基於字符串的,而不是基於類型的(因為在 TypeScript 中還不能進行反射)。 盡管如此,它在我的應用程序中運行良好。

試試這個依賴注入器(Typejector)

GitHub打字機

使用新的 TypeScript 1.5,可以使用注釋方式

例如

    @injection
    class SingletonClass {
        public cat: string = "Kitty";
        public dog: string = "Hot";

        public say() {
            alert(`${this.cat}-Cat and ${this.dog}-Dog`);
        }
    }
    @injection
    class SimpleClass {
        public say(something: string) {
            alert(`You said ${something}?`);
        }
    }

    @resolve
    class NeedInjectionsClass {
        @inject(SingletonClass)
        public helper: SingletonClass;
        @inject(SimpleClass)
        public simpleHelper: SimpleClass;

        constructor() {
            this.helper.say();
            this.simpleHelper.say("wow");
        }
    }
    class ChildClass extends NeedInjectionsClass {

    }

    var needInjection = new ChildClass();

對於問題案例:某些屬性應該實現偽接口(或抽象類),如下例所示。

    class InterfaceClass {
        public cat: string;
        public dog: string;

        public say() {

        }
    }

    @injection(true, InterfaceClass)
    class SingletonClass extends InterfaceClass {
        public cat: string = "Kitty";
        public dog: string = "Hot";

        public say() {
            alert(`${this.cat}-Cat and ${this.dog}-Dog`);
        }
    }

    @injection(true, InterfaceClass)
    class MockInterfaceClass extends InterfaceClass {
        public cat: string = "Kitty";
        public dog: string = "Hot";

        public say() {
            alert(`Mock-${this.cat}-Cat and Mock-${this.dog}-Dog`);
        }
    }

    @injection
    class SimpleClass {
        public say(something: string) {
            alert(`You said ${something}?`);
        }
    }

    @resolve
    class NeedInjectionsClass {
        @inject(InterfaceClass)
        public helper: InterfaceClass;
        @inject(SimpleClass)
        public simpleHelper: SimpleClass;

        constructor() {
            this.helper.say();
            this.simpleHelper.say("wow");
        }
    }

    class ChildClass extends NeedInjectionsClass {

    }

    var needInjection = new ChildClass();

注意:模擬注入應該在源代碼之后定義,因為它重新定義了接口的類創建器

對於使用 Angular2 的人,我開發了 Fluency Injection https://www.npmjs.com/package/fluency-injection 文檔非常完整,它模仿了 Angular2 的 DI 的行為。

非常感謝您的反饋,希望對您有所幫助:)

您可以使用以下解決方案:
JavaScript/TypeScript 的輕量級依賴注入容器

import {autoInjectable, container} from "tsyringe";

class MyService {
  move(){
    console.log('myService move 123', );
  }
}

class MyServiceMock {
  move(){
    console.log('mock myService move 777', );
  }
}

@autoInjectable()
export class ClassA {
  constructor(public service?: MyService) {
  }
  move(){
    this.service?.move();
  }
}

container.register(MyService, {
  useClass: MyServiceMock
});

new ClassA().move();

輸出:

模擬 myService 移動 777

我一直在開發一個名為Pigly的 DI 解決方案。 給出有關注入和測試的原始問題的示例(誠然不是自動模擬生成-盡管您可以像我在這里所做的那樣嘗試ts-auto-mock ):

鑒於:

interface IDb {
  set(key: string, value: string);
}

interface IApi {
  setName(name: string);
}

class Api implements IApi {
  constructor(private db: IDb) {}
  setName(name: string){
    this.db.set("name", name);
  }
}

我們可以綁定類型,

import { Kernel, toSelf, to, toConst } from 'pigly';
import * as sinon from 'sinon';

let spy = sinon.spy();

let kernel = new Kernel();

kernel.bind(toSelf(Api));
kernel.bind<IApi>(to<Api>());
kernel.bind<IDb>(toConst({ set: spy }));

然后解決並測試:

let api = kernel.get<IApi>();

api.setName("John");

console.log(spy.calledWith("name", "John"));

此示例的執行/編譯需要一個 typescript 轉換器 - 將接口符號和構造函數提供程序編譯成普通的 javascript。 有幾種方法可以做到這一點 ts-node + ttypescript 方法是有一個 tsconfig.json:

{
  "compilerOptions": {
    "target": "es2015",
    "module": "commonjs",
    "moduleResolution": "node",
    "plugins": [{
      "transform": "@pigly/transformer"
    }]
  }
}

並執行

ts-node --compiler ttypescript example-mock.ts

Pigly 的區別在於不需要對您的(或第三方)類進行任何更改,代價是使用打字稿轉換器,或者如果(您不想使用轉換器)則使用更詳細的綁定。 它仍然是實驗性的,但我認為它顯示出希望。

TypeScript 可以很好地與 AMD 加載器(如 requirejs)配合使用。 如果配置正確,TypeScript 將輸出完全符合 AMD 的 javascript。

在測試情況下,您可以配置 requirejs 以注入可測試的模塊。

你可以試一試: https ://www.npmjs.com/package/easy-injectionjs。 它是一個通用的依賴注入包。

@EasySingleton 在整個應用程序中創建依賴項的單個實例。 它是某種服務的理想選擇。

@EasyPrototype 根據需要創建盡可能多的依賴項實例。 它是可變依賴項的理想選擇。

@EasyFactory 主要用於繼承:

你可以使用這個包做任何事情:簡單用法(來自自述文件):

import { Easy, EasyFactory, EasyPrototype, EasySingleton } from 'easy-injectionjs';

@EasyFactory()
abstract class Person {
  abstract getName();
  abstract setName(v: string);
}

// @EasyObservable()
@EasySingleton()
class Somebody extends Person{
  // @Easy()
  constructor (private name: string) {
    super()
    this.name = 'Sal';
  }

  public getName() {
    return this.name;
  }
  public setName(v: string) {
    this.name = v;
  }
}

@EasyPrototype()
class Nobody extends Person{
  @Easy()
  somebody: Person;
  constructor () {
    super()
  }

  public getName() {
    return this.somebody.getName();
  }

  public setName(v: string) {
    this.somebody.setName(v);
  }
}

@EasyPrototype()
class Data {
  @Easy()
  somebody: Person;
  name: string;

  change(v: string) {
    this.somebody.setName(v);
  }

  getName(): string {
    return this.somebody.getName();
  }
}

let n = new Nobody();
console.log(n.getName()) // Prints Sal
n.setName('awesome');
console.log(n.getName())  // Prints awesome
let d = new Data()
console.log(d.getName())  // Prints awesome
d.change('Gelba')
console.log(n.getName())  // Prints Gelba
d.change('kaa')
console.log(n.getName())  // Prints Kaa

即使你想注入節點模塊,你也可以這樣做:

import * as IExpress from 'express';
import { Easy, EasySingleton } from 'easy-injectionjs';

@EasySingleton()
class Express extends IExpress {} 

@EasySingleton()
export class App {
  @Easy()
  private _express: Express;
}

let app = new App();
console.log(app)

當然,快速服務器的使用不適用於控制台日志記錄。 它僅用於測試:D。

希望有幫助:D

Dime是一個非常簡單的依賴注入庫。 不過,它還處於開發的早期階段,因此可能存在一些錯誤。 維基頁面上有更多信息。

示例用法:

import { ItemsService } from './items-service'; // ItemsService is an interface
import { Inject } from '@coined/dime';

class ItemsWidget {
    @Inject()
    private itemsService: ItemsService;

    render() {
        this.itemsService.getItems().subscribe(items => {
            // ...
        });
    }
}

// Setup
const appPackage = new Package("App", {
    token: "itemsService",
    provideClass: AmazonItemsService // AmazonItemsService implements ItemsService
});

Dime.mountPackages(appPackage);

// Display the widget
const widget = new ItemsWidget();
widget.render();

我從事受 AutoFixture 啟發的 AutoFixtureTS。 AutoFixtureTS 通過自動化不相關的測試夾具設置使 TypeScript 開發人員更容易進行測試驅動開發,使測試開發人員能夠專注於每個測試用例的基本要素。

http://ronniehegelund.github.io/AutoFixtureTS/

它仍然只是原型代碼,但請檢查一下:-)

/羅尼

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM