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

#angular #unit-testing #jasmine

#angular #модульное тестирование #jasmine

Вопрос:

Я пытаюсь протестировать свою службу Angular, и часть того, что делает служба, — это загрузка файла JSON, который используется в качестве конфигурации для службы. В тестах (сквозных console.log ) я подтвердил, что способ, которым я издеваюсь над вызовом 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.log в 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.log показывает, что в .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.log internalConfigData использую объект в 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');
});
  

Комментарии:

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

2. @Isaac я добавил массив TestBed.configureTestingModule с поставщиками в конец вопроса.

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

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

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

Ответ №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);
    });
}
  

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

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

Комментарии:

1. В этом случае она должна быть преобразована в обещание, поэтому использование async around the beforeEach работало отлично. Спасибо!

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