модульные тесты для компонента, использующего служебный класс со статическими методами

У меня есть компонент, который использует класс LocaleService и статический метод этой службы instant(). LocaleService не вводится в компонент. При тестировании компонента меня не интересуют LocaleService внутреннее устройство и я не хочу его тестировать. Таким образом, в beforeEach я добавил шпиона

const localeServiceInstantSpy = spyOn(LocaleService, 'instant');
localeServiceInstantSpy.and.callFake(msg => msg);

Это хорошо работает. Теперь мне нужно переместить этого шпиона (и других) на заглушку LocaleService и использовать его в этом тесте и тестах других компонентов с использованием LocaleService - их много. Как лучше всего этого добиться? Как создать многоразовую LocaleServiceStub?

\app\utils\locale.service.ts

export class LocaleService {

    public static lang: string;

    private static messages = {
        'user.add': {
            en: 'Add Local User Account',
            de: 'Add Local User Account DE'
        },
        'user.edit': {
            en: 'Edit Local User Account',
            de: 'Edit Local User Account DE'
        }
    };

    public static instant(key: string) {
        return this.messages[key][this.lang];
    }

}

Использование в тестируемом классе \app\settings\users\user-form.component.ts

import { LocaleService } from 'app/utils/locale.service';
...
getDialogHeader() {
    return this.isNewUser ? LocaleService.instant('user.add') : LocaleService.instant('user.edit');
}
...

person koral    schedule 24.05.2018    source источник
comment
Пожалуйста, предоставьте stackoverflow.com/help/mcve для описываемой вами ситуации. к заглушке LocaleService - где это? и использовать его в этом тесте и тестах других компонентов с помощью LocaleService - что это за тесты?   -  person Estus Flask    schedule 25.05.2018
comment
@estus Я добавил упрощенный код.   -  person koral    schedule 25.05.2018


Ответы (1)


Классы, предназначенные только для статики, имеют запах кода в JavaScript. Если класс никогда не создается, в этом нет необходимости.

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

class LocaleService {
    public lang: string;

    private messages = {...};

    public instant(key: string) {
        return this.messages[key][this.lang];
    }
}

...
providers: [LocaleService, ...]
...

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

const LOCALE_SERVICE_MOCK = {
  provide: LocaleService,
  useFactory: () => ({
    instant: jasmine.createSpy('instant').and.callFake(msg => msg)
  })
};

И указать в испытательном стенде:

beforeEach(() => {
  TestBed.configureTestingModule({ providers: [LOCALE_SERVICE_MOCK]});
});

Или быть обернутым модулем:

beforeEach(() => {
  TestBed.configureTestingModule({ imports: [LocaleServiceMockModule]});
});

В текущем состоянии код можно сделать DRYer, переместив повторно используемый код в функцию:

function mockLocaleService() {
  const localeServiceInstantSpy = spyOn(LocaleService, 'instant');
  localeServiceInstantSpy.and.callFake(msg => msg);
}

И используя его там, где это необходимо:

beforeEach(mockLocaleService);
person Estus Flask    schedule 25.05.2018
comment
Спасибо за подробный ответ. Я знаю, что DI решит этот случай с помощью насмешливой инъекционной услуги. Я довольно часто использую его в других частях приложения. Этот LocaleService - один из немногих служебных классов, и разработчикам проще использовать их как статические. Подобно другим языкам. В противном случае мне пришлось бы вводить его почти в каждый компонент. Итак, в каждый компонент я бы добавил 6 служебных классов. Я сомневаюсь в достоинствах. Это нарушает реальную картину используемых сервисов. - person koral; 25.05.2018
comment
разработчикам проще использовать их как статические - это будет не класс, а объект. Но делать его статичным здесь - ошибка, потому что у него есть состояние. Скорее всего, это должен быть синглтон, что является вариантом использования поставщиков Angular. Вы можете поучиться у других реализаций Angular i18n, они реализовали это как услугу, потому что это имеет смысл. То, что вам нужно внедрять его повсюду, легко решить, в большинстве случаев вам нужно получать переводы в шаблонах, и это обрабатывается с помощью каналов, нет необходимости напрямую обращаться к сервису. github.com/ngx-translate/core близок к тому, что вы делаете. - person Estus Flask; 25.05.2018
comment
В любом случае, в его текущем состоянии (опять же, то, что вы не восстанавливаете LocaleService, является недостатком, потому что состояние lang сохраняется между тестами), вы можете сделать его СУХИМ, переместив spyOn(LocaleService, 'instant') в функцию и используя его как beforeEach(mockLocaleService) там, где это необходимо. - person Estus Flask; 25.05.2018
comment
Вы правы - lang сохраняется между тестами. Но я хочу тестировать не сам LocaleService, а компонент. А в шпионской функции меня не волнует значение Lang. Кроме того, у меня есть другие служебные классы, такие как форматирование. И я использую их в .ts также либо для подготовки данных для бэкэнда REST, либо для всплывающих сообщений диалоговых окон подтверждения. А LocaleService - это оболочка для службы от @ngx-translate/core - здесь удалено, чтобы упростить пример. - person koral; 28.05.2018
comment
пожалуйста, напишите свой комментарий, начинающийся с Любой путь ... в качестве ответа, который я должен принять. - person koral; 30.05.2018