angularcypressangular-modulecypress-component-test-runnere2e

Angular Cypress component test error: Cannot access '<Component>' before initialization


I am trying to run spec for my Angular component using Cypress. But I've got the following error in Cypress logs:

The following error originated from your test code, not from Cypress.


  > Cannot access 'TextsFilterComponent' before initialization


When Cypress detects uncaught errors originating from your test code it will automatically fail the current test.

Cypress could not associate this error to any specific test.


We dynamically generated a new test to display this failure.
View stack trace
 Print to console
    at Module.TextsFilterComponent (http://localhost:8080/__cypress/src/spec-1.js:80491:69)
    at 36283 (webpack://angular/./src/app/shared/components/games/texts-filter/texts-filter.module.ts:12:17)
    at __webpack_require__ (http://localhost:8080/__cypress/src/runtime.js:23:42)
    at 70275 (http://localhost:8080/__cypress/src/spec-1.js:80476:78)
    at __webpack_require__ (http://localhost:8080/__cypress/src/runtime.js:23:42)
    at 44549 (http://localhost:8080/__cypress/src/spec-1.js:80241:78)
    at __webpack_require__ (http://localhost:8080/__cypress/src/runtime.js:23:42)
    at 37511 (http://localhost:8080/__cypress/src/spec-1.js:79929:69)
    at __webpack_require__ (http://localhost:8080/__cypress/src/runtime.js:23:42)
    at 67953 (http://localhost:8080/__cypress/src/spec-1.js:27564:82)
    at __webpack_require__ (http://localhost:8080/__cypress/src/runtime.js:23:42)
    at 70111 (http://localhost:8080/__cypress/src/spec-1.js:26774:74)
    at __webpack_require__ (http://localhost:8080/__cypress/src/runtime.js:23:42)
    at 12063 (http://localhost:8080/__cypress/src/spec-1.js:17404:67)
    at __webpack_require__ (http://localhost:808

My spec looks like this:

import { CommonModule } from '@angular/common';
import { FormControl, FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms';
import { NgSelectModule } from '@ng-select/ng-select';
import { TranslateModule } from '@ngx-translate/core';
import { FormFieldModule } from '../form-field/form-field.module';
import { TextsFilterComponent } from './texts-filter.component';
import { TextsFilterModule } from './texts-filter.module';
import { MatInputModule } from '@angular/material/input';
import { MatSelectModule } from '@angular/material/select';
import {
  ChangeDetectorRef, EventEmitter,
} from '@angular/core';
import { TextsService, SyllablesService, WordsService } from '../../../../core/services/games';
import { AuthService } from '../../../../core/services';

it('mounts', () => {
  cy.mount(TextsFilterComponent, {
    imports: [
      TextsFilterModule,
      CommonModule,
      FormFieldModule,
      FormsModule,
      ReactiveFormsModule,
      NgSelectModule,
      TranslateModule,
      MatInputModule,
      MatSelectModule,
    ],
    providers: [
      TextsService,
      ChangeDetectorRef,
      AuthService,
      WordsService,
      SyllablesService,
    ],
    componentProperties: {
      parametersForm: new FormGroup({
        test: new FormControl('x'),
      }),
      hasError: new EventEmitter(),
      customMinItems: 1,
    },
  });
});

My spec is in the same folder with component, so all imports are correct.

I've placed all services injected to my component in the providers. And all imports from module to imports. Also I passed some values for 2 inputs and 1 output of the component in the componentProperties section.

Componennt's module:

import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { NgSelectModule } from '@ng-select/ng-select';
import { TranslateModule } from '@ngx-translate/core';
import { FormFieldModule } from '../form-field/form-field.module';
import { TextsFilterComponent } from './texts-filter.component';
import { MatInputModule } from '@angular/material/input';
import { MatSelectModule } from '@angular/material/select';

@NgModule({
  declarations: [TextsFilterComponent],
  exports: [TextsFilterComponent],
  imports: [
    CommonModule,
    FormFieldModule,
    FormsModule,
    ReactiveFormsModule,
    NgSelectModule,
    TranslateModule,
    MatInputModule,
    MatSelectModule,
  ],
})
export class TextsFilterModule {}

And my component:

// ...

@Component({
  selector: 'texts-filter',
  templateUrl: './texts-filter.component.html',
  styleUrls: ['./texts-filter.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class TextsFilterComponent
  extends AbstractFiltersMixin(BaseFilters)
  implements OnDestroy, AfterViewInit, OnChanges {

  constructor(
    private _textsService: TextsService,
    private _cdr: ChangeDetectorRef,
    private _authService: AuthService,
    private _wordsService: WordsService,
    private _syllablesService: SyllablesService,
  ) {
    super();
  }

  // ...
}

And my cypress/support/component.ts:

import { mount } from 'cypress/angular'


declare global {
  namespace Cypress {
    interface Chainable {
      mount: typeof mount
    }
  }
}

Cypress.Commands.add('mount', mount)

Solution

  • The main problem was in index.ts file. I've used them to export all services from the folder to simplify imports:

    index.ts

    export * from './api.service';
    export * from './audio.service';
    export * from './auth.service';
    

    But this approach leads to circullar dependencies.

    The best solution is to get ride of index.ts and import each service from it's file.

    But in my case I have already had thousands of imports and I didn't want to fix all of them. So, I found another solution that helped my Jasmine and Cypress tests run.

    I've added "emitDecoratorMetadata": false property to tsconfig.json and error disappeared!

    tsconfig.json

    {
       ...,
       "compilerOptions": {
          ...
          "emitDecoratorMetadata": false,
        },
    }