Функция inject, представленная в Angular 14, даже с ограничениями — очень крутая функция. Но где свет, там и тьма. Когда мы внедряем все в конструктор класса, его легко протестировать, когда вы не используете TestBed. После использования инъекции это может быть немного сложно.

К счастью для нас, это не невозможно. Более того, очень легко сделать код с inject тестируемым.

Репозиторий

Как всегда, если вам нужен мой код, вы найдете его в репозитории на GitHub: github.com/galczo5/testable-inject. Чтобы упростить тестирование моего кода в ваших случаях, я опубликовал его на NPM. Вот ссылка: npmjs.com/package/testable-inject.

Проблема

Основная проблема с inject в том, что это функция. Многие средства запуска тестов предоставляют решения для фиктивных функций и т. д. Лично мне нравится, чтобы мой код не отличался от средств выполнения тестов, поэтому я могу перенести его из Karma в Jest или, может быть, даже во что-то другое.

Чтобы решить эту проблему, мы можем просто обернуть функцию inject. Обертка должна иметь какой-то контекст. Из соображений опыта разработчиков я сделаю его статическим.

import {isDevMode, inject as angularInject, ProviderToken} from "@angular/core";

export class Inject {

  private static map: Map<ProviderToken<any>, any> = new Map<ProviderToken<unknown>, unknown>();

  static mock<T>(token: ProviderToken<T>, value: T) {
    this.map.set(token, value);
  }

  static get<T>(token: ProviderToken<T>): T {
    return this.map.get(token);
  }

  static has<T>(token: ProviderToken<T>): boolean {
    return this.map.has(token);
  }

}

export function inject<T>(token: ProviderToken<T>): T {
  if (isDevMode() && Inject.has<T>(token)) {
      return Inject.get(token);
  }

  return angularInject<T>(token);
}

Inject класс - это мой контекст здесь. С этим классом легко издеваться над чем угодно. В дополнение к контексту я добавил свою функцию inject, которая имеет тот же API, что и исходная функция из пакета @angular/core. Эта функция является моей оболочкой. Снаружи все, что вам нужно сделать, это изменить импорт в вашем коде, и он станет доступным для тестирования!

КСТАТИ. Он использует фиктивные значения только в режиме разработки, поэтому это не должно влиять на производственную сборку. Даже если и влияет, разница будет близка к нулю миллисекунд.

Как использовать

Допустим, у меня есть очень простой компонент для тестирования.

import {Component, ElementRef, Renderer2} from "@angular/core";
import {inject} from "testable-inject";

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {

  constructor(elementRef: ElementRef) {
    const renderer = inject(Renderer2);
    renderer.addClass(elementRef.nativeElement, 'class');
  }

}

Единственное, что нужно проверить здесь, это вызов renderer.addClass, чтобы проверить, был ли добавлен класс или нет. Обратите внимание, что ссылка inject исходит из пакета testable-inject, а не из @angular/core.

Это всего лишь пример, поэтому я решил не тратить здесь время, и код модульного теста упрощен.

import {AppComponent} from "./app.component";
import {Inject} from "testable-inject";
import {Renderer2} from "@angular/core";

describe('AppComponent', () => {

  it('Should mock inject', () => {

    let itWorked = false;

    Inject.mock(Renderer2, {
      addClass() {
        itWorked = true;
      }
    } as unknown as Renderer2)

    new AppComponent({ nativeElement: null });

    expect(itWorked).toBeTruthy();
  });

});

Заключение

Вы можете (и должны) тестировать код с помощью inject вызовов функций. Это просто и выглядит так же, как вызов функции Angular.

Весь код для этого опубликован в этой статье. Тот же код вы найдете в репозитории и в пакете NPM.