Модульное тестирование/издевательство над свойствами окна в Angular2 (TypeScript)

Я создаю несколько модульных тестов для службы в Angular2.

В моей службе у меня есть следующий код:

var hash: string; hash = this.window.location.hash;

Однако, когда я запускаю тест, содержащий этот код, он не работает.

Было бы здорово использовать все функции Window, но, поскольку я использую PhantomJs, я не думаю, что это возможно (я также пробовал Chrome, который дает те же результаты).

В AngularJs я бы прибегнул к насмешке над $Window (или, по крайней мере, над рассматриваемыми свойствами), но, поскольку документации для модульного тестирования Angular2 не так много, я не уверен, как это сделать.

Кто-нибудь может помочь?


person Rhys    schedule 12.04.2016    source источник
comment
Кажется, это довольно просто. Вероятно, проблема XY, потому что маршрутизатор уже абстрагирует хеш, абстракция поднимается до местоположение DOM.   -  person Estus Flask    schedule 12.04.2016


Ответы (4)


В Angular 2 вы можете использовать функцию @Inject() для внедрения объекта окна, назвав его с помощью токена строки, например

  constructor( @Inject('Window') private window: Window) { }

Затем в @NgModule вы должны предоставить его, используя ту же строку:

@NgModule({
    declarations: [ ... ],
    imports: [ ... ],
    providers: [ { provide: 'Window', useValue: window } ],
})
export class AppModule {
}

Затем вы также можете издеваться над ним, используя строку токена

beforeEach(() => {
  let windowMock: Window = <any>{ };
  TestBed.configureTestingModule({
    providers: [
      ApiUriService,
      { provide: 'Window', useFactory: (() => { return windowMock; }) }
    ]
  });

Это работало в Angular 2.1.1, последней версии от 28.10.2016.

Не работает с Angular 4.0.0 AOT. https://github.com/angular/angular/issues/15640

person Klas Mellbourn    schedule 29.10.2016
comment
Это работает для тестирования, но не для компиляции AoT (компилируется, но с предупреждением, и приложение вылетает в браузере) - person Dunos; 29.12.2016
comment
Пробовал это со сборкой AoT (наряду с вводом window как any внутри конструктора, чтобы обойти другую ошибку), но моя производственная сборка дала сбой, когда я попытался получить доступ к пользовательскому свойству window, которое я устанавливаю в другом файле. Вместо этого я следовал решению здесь, и оно сработало: stackoverflow.com/a/37176929/1683187 - person Timespace; 08.05.2017
comment
Это правильный ответ для динамического изменения объекта windowMock в каждом тесте, когда это необходимо. - person Eugen Sunic; 28.08.2019

Как упомянул @estus в комментарии, вам лучше получить хэш от маршрутизатора. Но чтобы ответить на ваш вопрос напрямую, вам нужно внедрить окно в то место, где вы его используете, чтобы во время тестирования вы могли его издеваться.

Во-первых, зарегистрируйте окно у поставщика angular2 - возможно, где-то в глобальном масштабе, если вы используете это повсюду:

import { provide } from '@angular/core';
provide(Window, { useValue: window });

Это сообщает angular, когда внедрение зависимостей запрашивает тип Window, он должен возвращать глобальный window.

Теперь, в том месте, где вы его используете, вы вводите это в свой класс вместо того, чтобы напрямую использовать глобальный:

import { Component } from '@angular/core';

@Component({ ... })
export default class MyCoolComponent {
    constructor (
        window: Window
    ) {}

    public myCoolFunction () {
        let hash: string;
        hash = this.window.location.hash;
    }
}

Теперь вы готовы смоделировать это значение в своем тесте.

import {
    beforeEach,
    beforeEachProviders,
    describe,
    expect,
    it,
    inject,
    injectAsync
} from 'angular2/testing';

let myMockWindow: Window;
beforeEachProviders(() => [
    //Probably mock your thing a bit better than this..
    myMockWindow = <any> { location: <any> { hash: 'WAOW-MOCK-HASH' }};
    provide(Window, {useValue: myMockWindow})
]);

it('should do the things', () => {
    let mockHash = myMockWindow.location.hash;
    //...
});
person elwyn    schedule 12.05.2016
comment
Вы также можете просто ввести constructor(window:Window) и предоставить его как provide(Window, {useValue: window}) или provide(Window, {useClass: MyWindowMock}). Нет необходимости использовать строковый ключ, если есть доступный тип. - person Günter Zöchbauer; 13.05.2016
comment
удалено в RC6 из ядра, измените его на providers: [ {provide: Window, useValue: window}, ] - person Anand Rockzz; 07.05.2017
comment
Это спасатель. Позволил мне смоделировать нужные мне тесты без перенаправления страницы. Работал в Ангуляр 11. - person Cuga; 05.03.2021

После RC4 метод provide() устарел, поэтому способ справиться с этим после RC4:

  let myMockWindow: Window;

  beforeEach(() => {
    myMockWindow = <any> { location: <any> {hash: 'WAOW-MOCK-HASH'}};
    addProviders([SomeService, {provide: Window, useValue: myMockWindow}]);
  });

Мне нужно время, чтобы понять, как это работает.

person ulou    schedule 29.08.2016
comment
Этот синтаксис устарел в версии выпуска Angular2. - person Klas Mellbourn; 30.10.2016

Я действительно не понимаю, почему никто не предоставил самое простое решение, которое рекомендуется командой Angular для тестирования службы, как вы можете видеть здесь. В большинстве случаев вам даже не нужно иметь дело с материалами TestBed.

Кроме того, вы можете использовать этот подход для компонентов и директив. В этом случае вы создадите не экземпляр компонента, а экземпляр класса. Это означает, что вам также не нужно иметь дело с дочерними компонентами, используемыми в шаблоне компонентов.

Предполагая, что вы можете внедрить Window в свой конструктор

constructor(@Inject(WINDOW_TOKEN) private _window: Window) {}

Просто сделайте следующее в вашем файле .spec:

describe('YourService', () => {
  let service: YourService;
  
  beforeEach(() => {
    service = new YourService(
      {
        location: {hash: 'YourHash'} as any,
        ...
      } as any,
      ...
    );
  });
}

Меня не интересуют другие свойства, поэтому я обычно добавляю приведение типа к any. Не стесняйтесь включать все другие свойства и вводить их соответствующим образом.

Если вам нужны другие значения для фиктивных свойств, вы можете просто следить за ними и изменять значение, используя returnValue жасмина:

const spy: any = spyOn((service as any)._window, 'location').and.returnValue({hash: 'AnotherHash'});

or

const spy: any = spyOn((service as any)._window.location, 'hash').and.returnValue('AnotherHash');
person Ilker Cat    schedule 02.12.2020