测试 angular 服务 return 值

Testing angular Services return Values

需要帮助以了解这里发生的事情:

一个基本的英雄之旅app就可以解释了,

我想用 Jest 设置一些测试,以便能够查看服务的行为是否不随时间改变。

这里是测试文件的样子:

import { TestBed } from '@angular/core/testing';

import { HeroService } from './hero.service';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { ServicesModule } from '@services/services.module';

describe('HeroService', () => {
  let httpMock: HttpTestingController;
  let service: HeroService;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [
        ServicesModule,
        HttpClientTestingModule
      ],
    });
    httpMock = TestBed.inject(HttpTestingController);
    service = TestBed.inject(HeroService);
  });

  afterEach(() => {
    httpMock.verify();
  });

  it('should be created', () => {
    expect(service).toBeTruthy();
  });

  // Do not pass, timeout error ( spec #1 )
  it('getHeroes: should return a sorted list',  done => {
    service.getHeroes().subscribe(heroes => {
      expect(heroes.length).toBe(10);
      done();
    } );

    // Simulates the asynchronous passage of time

    const req = httpMock.expectOne(`api/heroes`);
    expect(req.request.method).toBe('GET');

  });

  // Do pass but don't check the value ( spec #2 )
  it('getHeroes: should return a sorted list', () => {
    service.getHeroes().subscribe(heroes => {
      expect(heroes.length).toBe(10);
    } );

    const req = httpMock.expectOne(`api/heroes`);
    expect(req.request.method).toBe('GET');

  });

});

规范 1 错误消息:

: Timeout - Async callback was not invoked within the 5000 ms timeout specified by jest.setTimeout.
Timeout - Async callback was not invoked within the 5000 ms timeout specified by jest.setTimeout.Error

规范 2:

测试显示为绿色并通过但 expect(heroes.length).toBe(10);未正确检查。

我有一个基本的 InMemoryDbService,它的数据库是这样设置的(所以长度应该是 4)并且没有通过之前的测试:

function getDbData() : Db{
  const heroes: any[] = [
    {
      id: 11,
      name: 'Maxwell Smart',
      saying: 'Missed it by that much.'
    },
    {
      id: 12,
      name: 'Bullwinkle J. Moose',
      saying: 'Watch me pull a rabbit out of a hat.'
    },
    {
      id: 13,
      name: 'Muhammad Ali',
      saying: 'Float like a butterfly, sting like a bee.'
    },
    {
      id: 14,
      name: 'Eleanor Roosevelt',
      saying: 'No one can make you feel inferior without your consent.'
    }
  ];

  return {heroes} as Db;
}

像主应用模块中那样导入:

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    // Core App Module
    CoreModule,
    // Routing Module
    AppRoutingModule,
    // Angular Specifics Module
    BrowserModule,
    HttpClientModule,
    // Development purpose Only
    HttpClientInMemoryWebApiModule.forRoot(
      InMemoryDataService, {
        dataEncapsulation: false,
        passThruUnknownUrl: true
      }
    ),
  ],
  providers: [ServicesModule],
  bootstrap: [AppComponent]
})

好的措施:Hero.service.ts:

import { Injectable } from '@angular/core';
import { Hero } from '@models/hero.model';
import { HEROES } from '@services/in-memory-data/mock-heroes.service';


import { Observable, of } from 'rxjs';
import { catchError, map, tap } from 'rxjs/operators';
import { MessageService } from '@services/messages/message.service';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { ErrorHandlerService } from '@services/error-handler/error-handler.service';

// Marks the class as one that participates in the dependency injection system
// This method don't need a link in service.module
/*//
@Injectable({
  providedIn: 'root'
})
//*/

// This method need a link in service.module
@Injectable()
export class HeroService {

  private heroesUrl = 'api/heroes';  // endpoint of the api service

  // TODO : HTTPInterceptor
  httpOptions = {
    headers: new HttpHeaders({'Content-Type': 'application/json'})
  };

  constructor(
    private http: HttpClient,
    private messageService: MessageService,
    private errorHandlerService: ErrorHandlerService) {
  }

  /** GET heroes from the server */
  getHeroes(): Observable<Hero[]> {
    return this.http.get<Hero[]>(this.heroesUrl)
      .pipe(
        tap(_ => this.messageService.add('fetched heroes')),
        catchError(this.errorHandlerService.handleError<Hero[]>('getHeroes', []))
      );
  }

  /** GET hero by id. Will 404 if id not found */
  getHero(id: number): Observable<Hero> {
    const url = `${this.heroesUrl}/${id}`;
    return this.http.get<Hero>(url).pipe(
      tap(_ => this.messageService.add(`fetched hero id=${id}`)),
      catchError(this.errorHandlerService.handleError<Hero>(`getHero id=${id}`))
    );
  }

  /** PUT: update the hero on the server */
  updateHero(hero: Hero): Observable<any> {
    return this.http.put(this.heroesUrl, hero, this.httpOptions).pipe(
      tap(_ => this.messageService.add(`updated hero id=${hero.id}`)),
      catchError(this.errorHandlerService.handleError<any>('updateHero'))
    );
  }

  /** POST: add a new hero to the server */
  addHero(hero: Hero): Observable<Hero> {
    return this.http.post<Hero>(this.heroesUrl, hero, this.httpOptions).pipe(
      tap((newHero: Hero) => this.messageService.add(`added hero w/ id=${newHero.id}`)),
      catchError(this.errorHandlerService.handleError<Hero>('addHero'))
    );
  }

  /** DELETE: delete the hero from the server */
  deleteHero(hero: Hero | number): Observable<Hero> {
    const id = typeof hero === 'number' ? hero : hero.id;
    const url = `${this.heroesUrl}/${id}`;

    return this.http.delete<Hero>(url, this.httpOptions).pipe(
      tap(_ => this.messageService.add(`deleted hero id=${id}`)),
      catchError(this.errorHandlerService.handleError<Hero>('deleteHero'))
    );
  }

  /* GET heroes whose name contains search term */
  searchHeroes(term: string): Observable<Hero[]> {
    if (!term.trim()) {
      // if not search term, return empty hero array.
      return of([]);
    }
    return this.http.get<Hero[]>(`${this.heroesUrl}/?name=${term}`).pipe(
      tap(x => x.length ?
        this.messageService.add(`found heroes matching "${term}"`) :
        this.messageService.add(`no heroes matching "${term}"`)),
      catchError(this.errorHandlerService.handleError<Hero[]>('searchHeroes', []))
    );
  }
}

谁能帮我找出问题所在以及如何找到解决方案?

谢谢。

这里使用 done 回调是正确的方法。 问题是您没有定义 httpMock:

的 return 值
it('getHeroes: should return a sorted list',  done => {
    service.getHeroes().subscribe(heroes => {
      expect(heroes.length).toBe(4);
      done();
    } );

    // Simulates the asynchronous passage of time

    const req = httpMock.expectOne(`api/heroes`);
    expect(req.request.method).toBe('GET');

    // Mock the return value of http.get
    req.flush([
    {
      id: 11,
      name: 'Maxwell Smart',
      saying: 'Missed it by that much.'
    },
    {
      id: 12,
      name: 'Bullwinkle J. Moose',
      saying: 'Watch me pull a rabbit out of a hat.'
    },
    {
      id: 13,
      name: 'Muhammad Ali',
      saying: 'Float like a butterfly, sting like a bee.'
    },
    {
      id: 14,
      name: 'Eleanor Roosevelt',
      saying: 'No one can make you feel inferior without your consent.'
    }
    ]);
    
    // Verify that there are no outstanding calls
    httpMock.verify();

  });