#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 thebeforeEach
работало отлично. Спасибо!2. просто любопытно, почему для этого требуется быть обещанием?