Переменная Angular Service Member недоступна в модульных тестах

Я пытаюсь протестировать свою службу Angular, и часть того, что делает служба, — это загрузка файла JSON, который используется в качестве конфигурации для службы. Я подтвердил в тестах (через console.info), что способ, которым я имитирую вызов HTTP.get для получения конфигурации, работает и возвращает имитированный объект конфигурации:

// mocking the loading of the configuration in the beforeEach
authenticationService.loadInternalAuthModuleConfiguration();
const req = httpTestingController.expectOne('./internal-config.json');
req.flush({
    redirectStorage: 'testing-redirect',
    forbiddenStorage: 'testing-forbidden',
    useAuthServerInDevelopment: true,
});
httpTestingController.verify();

Когда я console.info в функции loadInternalAuthModuleConfiguration, я вижу объект и информацию из req.flush, показанных выше. В функции load он берет этот объект конфигурации и устанавливает его значение в частную переменную в службе:

loadInternalAuthModuleConfiguration() {
    return this._http
        .get(this.authConfig.internalAuthModuleConfigUrl)
        .toPromise()
        .then((configData: any) => {
            this.internalConfigData = { ...configData };
            this.internalConfigSubject.next(this.internalConfigData);
            this.setPrivateClassVariables();
        })
        .catch((err: any) => {
            this.internalConfigData = null;
            this.internalConfigSubject.next(this.internalConfigData);
        });
}

Опять же, console.info показывает, что в методе .then выше, что configData возвращается правильно и что он установлен как this.internalConfigData. Моя проблема заключается в следующем шаге.

Я хочу проверить, могу ли я получить доступ к значению этого объекта configData после его установки. (Помните, что я запускал функцию load в beforeEach.) У меня есть функция в сервисе, getInternalConfig и getInternalConfigValueByKey, которая либо возвращает весь объект конфигурации, либо значение для указанного ключа. Когда я запускаю это в тесте, я получаю undefined для объекта internalConfigData и для значения переданного ключа.

it('should be using testing-redirect as the redirectStorage', () => {
    const configObj = authenticationService.getInternalConfig();
    const redirectStorage = authenticationService.getInternalConfigValueByKey('redirectStorage');
    expect(redirectStorage).toBe('testing-redirect');
});

Этот тест должен пройти. Если я console.infointernalConfigData объект в load функции, я могу увидеть объект, который я ему дал. Я не уверен, почему кажется, что this.internalConfigData теряет свои данные где-то между beforeEach и запуском моего теста.

Что мне здесь не хватает, чтобы убедиться, что этот тест работает правильно и проходит?

Редактировать

Вот TestBed.configureTestingModule для справки:

TestBed.configureTestingModule({
    imports: [HttpClientTestingModule],
    providers: [
        AuthenticationService,
        { provide: AuthenticationConfig, useValue: mockAuthConfig },
        { provide: OidcConfigService, useValue: mockOidcConfigService },
        { provide: OidcSecurityService, useValue: mockOidcSecurityService },
        { provide: localStorage, useValue: mockLocalStorage },
    ],
});

Редактировать 2

Вот весь beforeEach и соответствующий тест:

beforeEach(() => {
    mockOidcConfigService = jasmine.createSpyObj(['load']);
    mockOidcSecurityService = jasmine.createSpyObj(['getIsAuthorized']);

    TestBed.configureTestingModule({
        imports: [HttpClientTestingModule],
        providers: [
            AuthenticationService,
            { provide: AuthenticationConfig, useValue: mockAuthConfig },
            { provide: OidcConfigService, useValue: mockOidcConfigService },
            { provide: OidcSecurityService, useValue: mockOidcSecurityService },
            { provide: localStorage, useValue: mockLocalStorage },
        ],
    });

    httpTestingController = TestBed.get(HttpTestingController);
    authenticationService = TestBed.get(AuthenticationService);

    store = {};

    authenticationService.loadInternalAuthModuleConfiguration();
    const req = httpTestingController.expectOne('./internal-config.json');
    req.flush({
        redirectStorage: 'testing-redirect',
        forbiddenStorage: 'testing-forbidden',
        useAuthServerInDevelopment: true,
    });
    httpTestingController.verify();
});

it('should be using testing-redirect as the redirectStorage', () => {
    const configObj = authenticationService.getInternalConfig();
    const redirectStorage = authenticationService.getInternalConfigValueByKey('redirectStorage');
    expect(redirectStorage).toBe('testing-redirect');
});

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

Isaac 20.04.2019 23:01

@Isaac Исаак Я добавил TestBed.configureTestingModule с массивом провайдеров в конец вопроса.

pjlamb12 20.04.2019 23:49

Во-первых, я использую тестовый стенд и рекомендую не использовать его. Но раз уж вы хотите, мой следующий вопрос: как вы связываетесь со службой, чтобы задавать ей вопросы?

Joseph Eames 21.04.2019 02:47

@JosephEames Я добавил еще немного кода, чтобы показать это. Однако я скажу, что я не использую TestBed по какой-либо конкретной причине, кроме той, что была в тестовом файле, сгенерированном CLI. Если есть лучший способ сделать это, я открыт для него.

pjlamb12 21.04.2019 03:17

Это одна из ужасных проблем с CLI. тестовые файлы по умолчанию используют тестовый стенд. это глупо. Весь угловой код (за исключением шаблонов) представляет собой простые классы JS. вы можете проверить их, используя базовый жасмин/карму. Таким образом, испытательный стенд нужен только в том случае, если вы хотите протестировать шаблон с компонентом.

Joseph Eames 22.04.2019 07:45

кроме того, я даже не уверен, что тестирует этот тест. тест должен быть заявлением о функциональности части вашего приложения. Когда вы разрабатывали службу аутентификации, вы поняли, что одной из вещей, в которой она нуждалась, было «использовать тестирование-перенаправление в качестве хранилища перенаправления»? Бьюсь об заклад, это не было. Бьюсь об заклад, это было что-то вроде «он должен использовать redirectStorage, который ему дали». Я также немного смущен. проверяет ли этот тест метод getInternalConfig или метод loadInternalAuthModuleConfiguration?

Joseph Eames 22.04.2019 07:53

Кроме того, почему у вас есть первая строка кода в блоке it? вы ничего не утверждаете по этому поводу. Это результат временной зависимости? если нет, переместите тесты на него в свой собственный it()

Joseph Eames 22.04.2019 08:03

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

pjlamb12 22.04.2019 17:42

Ok. это имеет больше смысла. Хорошая сделка.

Joseph Eames 23.04.2019 18:12
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Angular и React для вашего проекта веб-разработки?
Angular и React для вашего проекта веб-разработки?
Когда дело доходит до веб-разработки, выбор правильного front-end фреймворка имеет решающее значение. Angular и React - два самых популярных...
Эпизод 23/17: Twitter Space о будущем Angular, Tiny Conf
Эпизод 23/17: Twitter Space о будущем Angular, Tiny Conf
Мы провели Twitter Space, обсудив несколько проблем, связанных с последними дополнениями в Angular. Также прошла Angular Tiny Conf с 25 докладами.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
Мое недавнее углубление в Angular
Мое недавнее углубление в Angular
Недавно я провел некоторое время, изучая фреймворк Angular, и я хотел поделиться своим опытом со всеми вами. Как человек, который любит глубоко...
Освоение Observables и Subjects в Rxjs:
Освоение Observables и Subjects в Rxjs:
Давайте начнем с основ и постепенно перейдем к более продвинутым концепциям в RxJS в Angular
1
9
1 252
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Проблема здесь в том, что вы преобразуете http Observable в Promise, и ваш тест становится асинхронным. Это означает, что к тому времени, когда код достигает оператора it, данные в вашем сервисе еще не разрешены.

Если бы вы использовали Observable, он бы прошел:

loadInternalAuthModuleConfiguration() {
  return this.http
    .get(this.authConfig.internalAuthModuleConfigUrl)
    .subscribe((configData: any) => {
      this.internalConfigData = {...configData};
      this.internalConfigSubject.next(this.internalConfigData);
      this.setPrivateClassVariables();
    }, (err: any) => {
      this.internalConfigData = null;
      this.internalConfigSubject.next(this.internalConfigData);
    });
}

Если вы все еще хотите преобразовать наблюдаемое в обещание, вам нужно дождаться выполнения всех микрозадач:

import { TestBed, async } from '@angular/core/testing';  
...
beforeEach(async(() => {
  ...
}));

В этом случае его нужно преобразовать в обещание, поэтому использование async вокруг beforeEach сработало отлично. Спасибо!

pjlamb12 21.04.2019 07:48

просто любопытно, почему это требует обещания?

Joseph Eames 22.04.2019 08:04

Другие вопросы по теме