Jasmine компилирует / создает компоненты, странно действующие с beforeAll / beforeEach

#angular #jasmine #karma-runner

#angular #jasmine #карма-бегун

Вопрос:

Я пишу какой-то модульный тест для своего приложения и столкнулся с довольно странной ситуацией. Я создал приложение angular и использую karma amp; jasmine для модульных / интеграционных тестов.

Дело вот в чем: я написал этот код

 describe("Body Container component", () => {
    let component: BodyContainerComponent;
    let fixture: ComponentFixture<BodyContainerComponent>;
    beforeEach(async () => {
        getTestBed().configureTestingModule({
            imports: [AppTestingModule]
        });
        await getTestBed().compileComponents();
        fixture = getTestBed().createComponent(BodyContainerComponent);
        component = fixture.componentInstance;
        component.ngOnInit();
        fixture.detectChanges();
    });
  

И это работает нормально, тесты компилируются и выполняются правильно (и проходят).
Я думаю, это занимает некоторое время, потому что каждый раз, когда testingModule настраивается заново и компоненты компилируются.

Итак, a попытался изменить это таким образом:

  beforeAll(async () => {
    getTestBed().resetTestingModule();
    getTestBed().configureTestingModule({
        imports: [AppTestingModule]
    });
    await getTestBed().compileComponents();
});

beforeEach(async () => {
    fixture = getTestBed().createComponent(BodyContainerComponent);
    component = fixture.componentInstance;
    component.ngOnInit();
    fixture.detectChanges();
});
  

Эта версия той же логики / API, вызванная, однако, с ошибкой при getTestBed().createComponent, завершается с ошибкой при:

  Error: Illegal state: Could not load the summary for directive BodyContainerComponent.
    error properties: Object({ ngSyntaxError: true })
        at syntaxError (http://localhost:9876/node_modules/@angular/compiler/fesm5/compiler.js?:2430:1)
        at CompileMetadataResolver.push../node_modules/@angular/compiler/fesm5/compiler.js.CompileMetadataResolver.getDirectiveSummary (http://localhost:9876/node_modules/@angular/compiler/fesm5/compiler.js?:18535:1)
        at JitCompiler.push../node_modules/@angular/compiler/fesm5/compiler.js.JitCompiler.getComponentFactory (http://localhost:9876/node_modules/@angular/compiler/fesm5/compiler.js?:25979:1)
        at CompilerImpl.push../node_modules/@angular/platform-browser-dynamic/fesm5/platform-browser-dynamic.js.CompilerImpl.getComponentFactory (http://localhost:9876/node_modules/@angular/platform-browser-dynamic/fesm5/platform-browser-dynamic.js?:162:1)
        at TestingCompilerImpl.push../node_modules/@angular/platform-browser-dynamic/fesm5/testing.js.TestingCompilerImpl.getComponentFactory (http://localhost:9876/node_modules/@angular/platform-browser-dynamic/fesm5/testing.js?:110:1)
        at TestBedViewEngine.push../node_modules/@angular/core/fesm5/testing.js.TestBedViewEngine.createComponent (http://localhost:9876/node_modules/@angular/core/fesm5/testing.js?:1905:1)
        at Object.<anonymous> (http://localhost:9876/src/test/integration/app/components/body-container.integration-spec.ts?:21:32)
        at step (http://localhost:9876/node_modules/tslib/tslib.es6.js?:97:1)
        at Object.next (http://localhost:9876/node_modules/tslib/tslib.es6.js?:78:45)
        at http://localhost:9876/node_modules/tslib/tslib.es6.js?:71:1
  

Я пробовал различные итерации и изменения, чтобы заставить это работать, но ошибка сохраняется.
После некоторого анализа я могу сказать:
1- async / await работает нормально, beforeach вызывается только после завершения beforeAll
2- каждый отдельный тест выдает указанную выше ошибку, даже первый (что очень странно, потому что логически первый не должен меняться ни на дюйм между двумя версиями

Одна вещь, которую я заметил, это то, что тесты первой версии выполняются случайным образом, тогда как вторая версия, похоже, следует порядку. Асинхронность в beforeach второй версии ничего не меняет, также я пробовал вызывать различные методы сброса / уничтожения, но, похоже, ни один из них не помогает.

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

РЕДАКТИРОВАТЬ: После дополнительного анализа, просмотрев экземпляр TestBed, я заметил, что реализация «только для beforeEach» имеет

 TestBedViewEngine{_instantiated: false, _compiler: TestingCompilerImpl{_compiler: CompilerImpl{_metadataResolver: ..., _delegate: ..., injector: ...}, _directiveResolver: MockDirectiveResolver{_reflector: ..., _directives: ...}, _pipeResolver: MockPipeResolver{_reflector: ..., _pipes: ...}, _moduleResolver: MockNgModuleResolver{_reflector: ..., _ngModules: ...}, _overrider: MetadataOverrider{_references: ...}}, _moduleRef: null, _moduleFactory: NgModuleFactory_{moduleType: function DynamicTestModule() { ... }, _bootstrapComponents: [], _ngModuleDefFactory: function(_l) { ... }}, _compilerOptions: [], _moduleOverrides: [], _componentOverrides: [], _directiveOverrides: [], _pipeOverrides: [], _providers: [], _declarations: [], _imports: [function AppTestingModule() { ... }], _schemas: [], _activeFixtures: [], _testEnvAotSummaries: function () { ... }, _aotSummaries: [], _templateOverrides: [], _isRoot: true, _rootProviderOverrides: [], platform: PlatformRef{_injector: StaticInjector{parent: ..., source: ..., _records: ...}, _modules: [], _destroyListeners: [], _destroyed: false}, ngModule: function BrowserDynamicTestingModule() { ... }}
  

в то время как beforeAll имеет:

 TestBedViewEngine{_instantiated: false, _compiler: null, _moduleRef: null, _moduleFactory: null, _compilerOptions: [], _moduleOverrides: [], _componentOverrides: [], _directiveOverrides: [], _pipeOverrides: [], _providers: [], _declarations: [], _imports: [], _schemas: [], _activeFixtures: [], _testEnvAotSummaries: function () { ... }, _aotSummaries: [], _templateOverrides: [], _isRoot: true, _rootProviderOverrides: [], platform: PlatformRef{_injector: StaticInjector{parent: ..., source: ..., _records: ...}, _modules: [], _destroyListeners: [], _destroyed: false}, ngModule: function BrowserDynamicTestingModule() { ... }}
  

Большая разница, которую я вижу по этому выводу, заключается в _compiler , который равен null и создается соответственно.
Этот тестовый «снимок» был сделан непосредственно перед вызовом createComponent

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

1. По-видимому, getTestBed() теряет состояния (или изменяет экземпляр, или что-то еще) при завершении вызова beforeAll. На самом деле, когда я перемещаю вывод журнала внутри beforeAll, я заметил, что _compiler не равен null, тогда как в beforeEach он равен null

2. к сожалению, эта потеря происходит, даже если вы сначала назначаете TestBed локальной переменной, и ничего не меняет, если вы используете вспомогательную функцию async () вместо ключевого слова typescript async

Ответ №1:

Я действительно обнаружил, что проблему можно найти здесьhttps://github.com/angular/angular/issues/12409

В принципе, подводя итог, angular фактически переопределяет функцию beforeEach, добавляя некоторое очень самоуверенное поведение. В частности, это написано прямо здесь:https://github.com/angular/angular/blob/master/packages/core/testing/src/before_each.ts#L24

По этой причине тестовый модуль, настроенный в beforeAll, фактически сбрасывается в beforeEach (перед вызовом createComponent). Вместо этого использование только beforeEach на самом деле работает, потому что модуль настроен после вызова reset.

Итак, либо вы используете beforeEach по назначению, жертвуя скоростью (мнение angular заключается в том, что предоставляемые сервисы должны быть свежими при каждом тестировании, что не имеет смысла для материалов без состояния), либо вы можете выбрать два разных решения: 1) повторно определите глобальный beforeEach, восстановив его по умолчанию, а затем верните поведение angular: это, конечно, взлом и довольно нестабилен и выдает случайные ошибки, поскольку angular предполагает, что модули являются свежими для его собственных внутренних компонентов 2) не используйте beforeEach в любом случае, настройте модуль в beforeAll, а затем требуйте все действия beforeEach для пользовательской функции, которую вы вызываете в каждом операторе it