Можно ли изменить или смоделировать контейнер Inversify, используемый классом Typescript в модульном тесте Jasmine? - PullRequest
0 голосов
/ 22 февраля 2019

У меня есть класс Typescript, который использует InversifyJS и Inversify Inject Decorators для внедрения службы в частную собственность.С функциональной точки зрения это нормально, но у меня возникли проблемы с выяснением, как его тестировать.Я создал упрощенную версию моей проблемы ниже.

В модульном тесте Жасмин, как я могу поменять впрыснутый RealDataService на FakeDataService?Если свойство не было частным, я мог бы создать компонент и назначить поддельную службу, но мне интересно, возможно ли это с помощью контейнера IOC.

Я изначально следовал этому этому примеру в рецептах InversifyJSpage , но быстро понял, что созданный ими контейнер не используется ни в одном тестируемом классе.Кроме того, большинство примеров кода, которые я вижу в документах InversifyJS , не описывают, как выполнить его модульное тестирование.

Вот упрощенная версия проблемы:

myComponent.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();
    }
}

dataService.ts

import { injectable } from "inversify";

export interface IDataService {
    get(): string;
}

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

Конфигурация IOC

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

1 Ответ

0 голосов
/ 07 марта 2019

Мне удалось решить эту проблему, импортировав контейнер из другого файла.Используя этот метод, вы будете писать разные контейнеры для каждой комбинации зависимостей, которые вы хотите внедрить в тест.Для краткости рассмотрим пример кода с воинами ниндзя, приведенный в документах 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!");
  });
});
...