【问题标题】:Dependency injection in TypeScriptTypeScript 中的依赖注入
【发布时间】:2013-04-10 18:27:09
【问题描述】:

我正在研究使用 TypeScript 进行 TDD 的可能性。 如果我用 TypeScript 编写测试,是否可以让 import 语句为我的测试类返回模拟? 还是用纯 JavaScript 编写测试并自己处理注入 AMD 的唯一可行方法?

【问题讨论】:

    标签: tdd requirejs typescript amd


    【解决方案1】:

    您可以使用解决方案:
    Lightweight dependency injection container for 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

    【讨论】:

      【解决方案2】:

      我一直在开发一个名为 Pigly 的 DI 解决方案。给出有关注入和测试的原始问题的示例(诚然不是自动模拟生成-尽管您可以尝试ts-auto-mockas I've done here):

      给定:

      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"));
      

      此示例的执行/编译需要一个打字稿转换器 - 将接口符号和构造函数提供程序编译为纯 JavaScript。 There are a few ways to do this。 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 转换器,或者如果(您不想使用转换器)则使用更详细的绑定.它仍然是实验性的,但我认为它显示出希望。

      【讨论】:

        【解决方案3】:

        您可以试一试: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

        【讨论】:

          【解决方案4】:

          对于使用 Angular2 的人,我开发了 Fluency Injection https://www.npmjs.com/package/fluency-injection。文档非常完整,它模仿了 Angular2 的 DI 的行为。

          非常感谢您的反馈,希望对您有所帮助:)

          【讨论】:

          • 正是我想要的。非常感谢!
          【解决方案5】:

          我开发了一个名为 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了解更多信息

          【讨论】:

            【解决方案6】:

            我在 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 中还不能实现反射)。尽管如此,它在我的应用程序中运行良好。

            【讨论】:

            【解决方案7】:

            试试这个Dependency Injector (Typejector)

            GitHub Typejector

            使用新的 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();
            

            注意: 模拟注入应该在源代码之后定义,因为它会重新定义接口的类创建器

            【讨论】:

            • 项目不再支持那些调查它的人
            【解决方案8】:

            我在 AutoFixtureTS 上工作,它的灵感来自 AutoFixture。 AutoFixtureTS 通过自动化不相关的测试夹具设置使 TypeScript 开发人员更容易进行测试驱动开发,从而使测试开发人员能够专注于每个测试用例的基本要素。

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

            它仍然只是原型代码,但请检查一下 :-)

            /罗尼

            【讨论】:

              【解决方案9】:

              TypeScript 可以很好地与 requirejs 等 AMD 加载器配合使用。如果配置正确,TypeScript 将输出完全符合 AMD 的 javascript。

              在测试情况下,您可以配置 requirejs 以注入可测试模块。

              【讨论】:

              • 但是有没有办法在不超出我的 TypeScript 气泡的情况下进行注入?
              • 取决于你的意思,我猜......我的应用程序中的每一点“javascript”都是我从我编写的 TypeScript 生成的。这些天我的大多数应用程序的目标是只使用 TypeScript。但我不知道你在这里问什么。
              • 好吧,我有点不清楚。我试图围绕 JS 和 TS 的二元性来思考问题。假设我有一个 B 类接收模块 A: import A = module('/path/to/A'); class B { // 使用 A 的方法 } 是否可以为模块 A 提供模拟,同时仍然让测试在 TypeScript 中完成代码完成?
              • 目前,对于纯 JS,我使用 Jasmine + RequireJS 和 Sinon 进行模拟。
              • 我开始意识到 TS 中的依赖注入与 JS 不同,当您也需要 TS 中的测试时。那么首选的方法是通过构造函数注入而不是“通过”定义/导入。然后剩下的问题是我如何将我的测试保存在不同的地方,并且在运行时(RequireJS)和编译时(用于类型检查)仍然使模块路径有效。我应该为此写一个新问题吗?
              猜你喜欢
              • 2020-02-05
              • 1970-01-01
              • 2016-07-19
              • 2015-08-28
              • 2014-12-04
              • 2017-01-15
              • 1970-01-01
              • 2020-02-13
              • 2015-11-05
              相关资源
              最近更新 更多