【问题标题】:Testing angular Services return Values测试角度服务返回值
【发布时间】:2020-10-05 15:32:25
【问题描述】:

需要帮助才能了解这里发生了什么:

一个基本的英雄之旅应用程序将对其进行解释,

我想用 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', []))
    );
  }
}

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

谢谢。

【问题讨论】:

    标签: angular typescript testing rxjs observable


    【解决方案1】:

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

    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();
    
      });
    
    

    【讨论】:

    • 天哪,好吧。所以我绝对需要模拟刷新的东西,并且不能在我的所有测试用例中使用 inmmemory api 作为参考。嗯,这是不切实际的,但你解决了我的问题。我不清楚。谢谢老哥
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-06-24
    • 1970-01-01
    • 2016-03-20
    • 1970-01-01
    • 1970-01-01
    • 2019-12-31
    • 1970-01-01
    相关资源
    最近更新 更多