5

我有一个 Typescript 类,它使用 InversifyJS 和Inversify Inject Decorators将服务注入私有属性。从功能上讲,这很好,但我在弄清楚如何对其进行单元测试时遇到了问题。我在下面创建了我的问题的简化版本。

在 Jasmine 单元测试中,我怎样才能用 a 替换注入RealDataServiceFakeDataService?如果该属性不是私有的,我可以创建组件并分配一个假服务,但我想知道这是否可以通过使用 IOC 容器来实现。

我最初在 InversifyJS 食谱页面中遵循了这个示例,但很快意识到他们创建的容器没有在任何被测试的类中使用。此外,我在InversifyJS文档中看到的大多数代码示例都没有介绍如何对其进行单元测试。

这是问题的简化版本:

我的组件.ts

import { lazyInject, Types } from "./ioc";
import { IDataService } from "./dataService";

export default class MyComponent {

    @lazyInject(Types.IDataService)
    private myDataService!: IDataService;

    getSomething(): string {
        return this.myDataService.get();
    }
}

数据服务.ts

import { injectable } from "inversify";

export interface IDataService {
    get(): string;
}

@injectable()
export class RealDataService implements IDataService {
    get(): string {
        return "I am real!";
    }
}

国际奥委会配置

import "reflect-metadata";
import { Container, ContainerModule, interfaces, BindingScopeEnum } from "inversify";
import getDecorators from "inversify-inject-decorators";

import { IDataService, RealDataService } from "./dataService";

const Types = {
    IDataService: Symbol.for("IDataService")
};

const iocContainerModule = new ContainerModule((bind: interfaces.Bind) => {
    bind<IDataService>(Types.IDataService).to(RealDataService);
});

const iocContainer = new Container();
iocContainer.load(iocContainerModule);
const { lazyInject } = getDecorators(iocContainer);
export { lazyInject, Types };

单元测试

import { Container } from "inversify";
import { Types } from "./ioc";
import MyComponent from "./myComponent";
import { IDataService } from "./dataService";

class FakeDataService implements IDataService {
    get(): string {
        return "I am fake!";
    }
}

describe("My Component", () => {
    let iocContainer!: Container;
    let myComponent!: MyComponent;

    beforeEach(() => {
        iocContainer = new Container();
        iocContainer.bind(Types.IDataService).to(FakeDataService);

        // How do I make myComponent use this iocContainer?
        // Is it even possible?

        myComponent = new MyComponent();
    });

    it("should use the mocked service", () => {
        const val = myComponent.getSomething();
        expect(val).toBe("I am fake!");
    });
});
4

1 回答 1

2

我能够通过从不同的文件导入容器来解决这个问题。使用此方法,您将为要注入测试的每种依赖项组合编写一个不同的容器。为简洁起见,假设 Inversify 文档给出了忍者战士的代码示例。

// src/inversify.prod-config.ts
import "reflect-metadata";
import { Container } from "inversify";
import { TYPES } from "./types";
import { Warrior, Weapon, ThrowableWeapon } from "./interfaces";
import { Ninja, Katana, Shuriken } from "./entities";

const myContainer = new Container();
myContainer.bind<Warrior>(TYPES.Warrior).to(Ninja);
myContainer.bind<Weapon>(TYPES.Weapon).to(Katana);
myContainer.bind<ThrowableWeapon>(TYPES.ThrowableWeapon).to(Shuriken);

export { myContainer };
// test/fixtures/inversify.unit-config.ts
import "reflect-metadata";
import {Container, inject, injectable} from "inversify";
import { TYPES } from "../../src/types";
import { Warrior, Weapon, ThrowableWeapon } from "../../src/interfaces";

// instead of importing the injectable classes from src,
// import mocked injectables from a set of text fixtures.
// For brevity, I defined mocks inline here, but you would
// likely want these in their own files.

@injectable()
class TestKatana implements Weapon {
  public hit() {
    return "TEST cut!";
  }
}

@injectable()
class TestShuriken implements ThrowableWeapon {
  public throw() {
    return "TEST hit!";
  }
}

@injectable()
class TestNinja implements Warrior {

  private _katana: Weapon;
  private _shuriken: ThrowableWeapon;

  public constructor(
    @inject(TYPES.Weapon) katana: Weapon,
    @inject(TYPES.ThrowableWeapon) shuriken: ThrowableWeapon
  ) {
    this._katana = katana;
    this._shuriken = shuriken;
  }

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

}

const myContainer = new Container();
myContainer.bind<Warrior>(TYPES.Warrior).to(TestNinja);
myContainer.bind<Weapon>(TYPES.Weapon).to(TestKatana);
myContainer.bind<ThrowableWeapon>(TYPES.ThrowableWeapon).to(TestShuriken);

export { myContainer };
// test/unit/example.test.ts
// Disclaimer: this is a Jest test, but a port to jasmine should look similar.

import {myContainer} from "../fixtures/inversify.unit-config";
import {Warrior} from "../../../src/interfaces";
import {TYPES} from "../../../src/types";

describe('test', () => {
  let ninja;

  beforeEach(() => {
    ninja = myContainer.get<Warrior>(TYPES.Warrior);
  });

  test('should pass', () => {
    expect(ninja.fight()).toEqual("TEST cut!");
    expect(ninja.sneak()).toEqual("TEST hit!");
  });
});
于 2019-03-07T17:47:59.787 回答