Angular Unit Testing с помощью Jasmine - Ошибка: добавьте аннотацию @NgModule

Aug 22 2020

Я пытаюсь написать Jasmine Unit Test (с Karma) для компонента Angular, который использует две службы и одну форму. Учебники по тестированию ( например, это из Angular Docs ) показывают только, как тестировать компонент с помощью одной службы, и почему-то я не могу заставить его работать с немного более сложным компонентом:

Мой компонент: user-login.component.ts:

Компонент имеет форму входа в систему, где пользователь может ввести свои учетные данные. OnSubmitЯ отправляю предоставленные учетные данные в службу аутентификации, которая обрабатывает http-запрос к моему API. Если HTTP-ответ от API имеет статус 200, он будет содержать токен входа (JWT), который я храню с другой службой, которую я назвал TokenStorageService:

import { Component, OnInit } from '@angular/core';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import { TokenStorageService } from '../../../_services/token-storage.service';
import { AuthenticationService } from '../../../_services/authentication.service';
import { AuthRequest } from '../../../_models/authRequest';

@Component({
  selector: 'app-user-login',
  templateUrl: './user-login.component.html',
  styleUrls: ['./user-login.component.scss']
})
export class UserLoginComponent implements OnInit {

  loginForm: FormGroup;

  constructor(private formBuilder: FormBuilder,
    private tokenStorage: TokenStorageService,
    private authService: AuthenticationService) { }

   ngOnInit() {
     this.loginForm = this.formBuilder.group({
       username: ['', Validators.compose([Validators.required])],
       password: ['', Validators.required]
     });
   }

  onSubmit() {
    this.authService.login({ 
      userName: this.loginForm.controls.username.value, 
      password: this.loginForm.controls.password.value
    })
    .subscribe(data => {  
      if (data.status === 200) {
        this.tokenStorage.saveToken(data.body)
        console.log("SUCCESS: logged in")
      } 
    }
    });
  }
}

Мой тест: user-login.component.spec.ts:

Итак, я понял, что три вещи, которые я предоставляю в конструкторе ( FormBuilder, TokenStorageServiceи AuthenticationService), я также должен предоставить в моем TestBed. И поскольку я действительно не хочу внедрять сервисы для модульного теста, я вместо этого использую Stub Services. Итак, я сделал это:

TestBed.configureTestingModule({
      imports: [{HttpClientTestingModule}],
      providers: [{provide: FormBuilder}, { provide: TokenStorageService, useValue: tokenStorageServiceStub }, { provide: AuthenticationService, useValue: authenticationServiceStub }

Тогда весь тест будет выглядеть так:

import { ComponentFixture, TestBed } from '@angular/core/testing';
import { UserLoginComponent } from './user-login.component';
import { FormBuilder } from '@angular/forms';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { TokenStorageService } from 'src/app/_services/token-storage.service';
import { AuthenticationService } from 'src/app/_services/authentication.service';

describe('UserLoginComponent', () => {
  let component: UserLoginComponent;
  let fixture: ComponentFixture<UserLoginComponent>;
  let tokenStorageServiceStub: Partial<TokenStorageService>;
  let authenticationServiceStub: Partial<AuthenticationService>;
  // let tokenStorageService;
  // let authenticationService;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [{HttpClientTestingModule}],
      providers: [{provide: FormBuilder}, { provide: TokenStorageService, useValue: tokenStorageServiceStub }, { provide: AuthenticationService, useValue: authenticationServiceStub } ],
      declarations: [ UserLoginComponent ]
    })
    fixture = TestBed.createComponent(UserLoginComponent);
    component = fixture.componentInstance;
    // tokenStorageService = TestBed.inject(TokenStorageService);
    // authenticationService = TestBed.inject(AuthenticationService);
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });
});

Я закомментировал 4 строки, так как считаю, что они ошибочны, но в примере с Angular Docs они также внедряют реальные сервисы, даже если они жестко говорят, что не хотят использовать настоящие сервисы в тесте. Я не понимаю эту часть в примере с Документами?

Но в любом случае я продолжаю получать это сообщение об ошибке:

Поскольку ошибка говорит о чем-то, @NgModuleя думаю, это может быть связано с моим app.module.tsфайлом? Вот мой app.module.ts:

@NgModule({
 declarations: [
   AppComponent,
   SidebarComponent,
   UsersComponent,
   DetailsComponent,
   ProductsComponent,
   UploadFileComponent,
   GoogleMapsComponent,
   AddUserComponent,
   ProductFormComponent,
   UserLoginComponent,
   EditUserComponent,
   ProductDetailsComponent,
   MessagesComponent,
   MessageDetailsComponent,
   ChatComponent,
   UploadMultipleFilesComponent,
   InfoWindowProductOverviewComponent,
   AddDormComponent,
   AddProductComponent
 ],
 imports: [
   BrowserModule,
   AppRoutingModule,
   HttpClientModule, 
   BrowserAnimationsModule,
   FormsModule,
   ReactiveFormsModule,
   ImageCropperModule,
   DeferLoadModule,
   //Angular Material inputs (spezielle UI Elemente)
   MatDatepickerModule,
   MatInputModule,
   MatNativeDateModule,
   MatSliderModule,
   MatSnackBarModule,
   MatSelectModule,
   MatCardModule,
   MatTooltipModule,
   MatChipsModule,
   MatIconModule,
   MatExpansionModule,
   MDBBootstrapModule,
   AgmCoreModule.forRoot({
     apiKey: gmaps_environment.GMAPS_API_KEY 
   })
  ],
  providers: [
   UploadFileService, 
   {provide: MAT_DATE_LOCALE, useValue: 'de-DE'},   
   {provide:HTTP_INTERCEPTORS, useClass:BasicAuthHttpInterceptorService, multi:true},
 ],   
 bootstrap: [AppComponent],
})
export class AppModule { }

Ответы

5 tilo Aug 25 2020 at 19:20

Прямо сейчас вы объявили только заглушки tokenStorageServiceStubи authenticationServiceStub, но вам нужно инициализировать их, прежде чем предоставлять. Что-то в этом роде:

tokenStorageServiceStub = {
  saveToken: () => {}
};


authenticationServiceStub = {
  login: () => of({status: 200, body: {}})
}

Кроме того, примите во внимание рекомендацию @PrincelsNinja.

4 Prince Aug 25 2020 at 19:08

Не могли бы вы удалить FormBuilderиз providersмассива в ваших тестовых случаях и ReactiveFormsModuleвместо этого импортировать .

TestBed.configureTestingModule({
      imports: [HttpClientTestingModule, ReactiveFormsModule],
      providers: [{ provide: TokenStorageService, useValue: tokenStorageServiceStub }, { provide: AuthenticationService, useValue: authenticationServiceStub }

Примечание. Не заключайте элементы импорта в фигурные скобки.

1 sehe Aug 25 2020 at 19:25

Ваш app.module.tsфайл не имеет значения при использовании тестового стенда. Пожалуйста, проверьте весь импорт ваших компонентов, поместите их в свой .configureTestBed-import и будьте особенно осторожны с директивами.

Я довольно часто получаю эту ошибку, когда отсутствует импорт.

1 SouritraDasGupta Sep 01 2020 at 00:21
  providers: [
              ReactiveFormsModule,
              { provide: TokenStorageService,
                useClass: {saveToken: (data: any) => data} },
              { provide: AuthenticationService,
                useClass: {
                           login: (loginDetails: any): Promise<any> => {
                                                   return {status: 200}}.toPromise(); } }
                          }
               },
             ]
                     

Кроме того, удалите импорт для TokenStorageService и AuthenticationService из ваших файлов спецификаций, чтобы проверить, не вызывают ли они первую неопределенную ошибку в модуле динамического тестирования.

uiTeam324 Aug 22 2020 at 14:55

Это может быть из-за того, что он не получает файлы по указанному вами пути. Используйте тот же путь, который указан в файле компонента, и попробуйте. В основном такая проблема возникает из-за неправильного пути к файлу, повторяющихся объявлений, а не объявлений и т. Д.

import { TokenStorageService } from '../../../_services/token-storage.service';
import { AuthenticationService } from '../../../_services/authentication.service';