Angular如何测试服务方法?

Posted

技术标签:

【中文标题】Angular如何测试服务方法?【英文标题】:Angular how to test service methods? 【发布时间】:2022-01-21 00:51:06 【问题描述】:

我正在努力学习如何编写测试。我不明白如何模拟和测试服务中的方法。

我当前的服务文件如下:

import Injectable from '@angular/core';
import HttpClient, HttpErrorResponse from '@angular/common/http';
import Observable, throwError from 'rxjs';
import catchError, map, tap from 'rxjs/operators';
import APICONFIG from 'src/app/config/api.config';
import Certificate from 'src/app/models';

const APIURL = `$APICONFIG.base/education/certificate`;

@Injectable()
export class CertificateService 

  constructor(private http: HttpClient) 
  

  getAllUserCertificates(uuid: string): Observable<Certificate []> 
    if (!uuid) 
      return new Observable<Certificate []>();
    
    return this.http.get<Certificate []>(`$APIURL/$uuid`).pipe(
      tap(data => console.log("All user certificates: ", JSON.stringify(data))),
      map(this.parseCertificateData),
      catchError(this.handleError)
    )
  ;

  saveUserCertificates(certificates: Certificate[]): Observable<Certificate[]> 
    return this.http.post<Certificate[]>(APIURL, certificates).pipe(
      tap(data => console.log("All saved user certificates: ", JSON.stringify(data))),
      map(this.parseCertificateData),
      catchError(this.handleError)
    )
  ;

  parseCertificateData(rawCertificates: any): Certificate[] 
    return Object.keys(rawCertificates).map(key => 
      let certificate = new Certificate(
        rawCertificates[key].model.institute,
        rawCertificates[key].model.name,
        rawCertificates[key].model.description,
        rawCertificates[key].model.achievementDate,
        rawCertificates[key].model.expirationDate,
        rawCertificates[key].model.url,
        rawCertificates[key].id
      );
      console.log(rawCertificates[key]);
      return certificate;
    );
  ;

  private handleError(err: HttpErrorResponse) 
    let errorMessage = "";
    if (err.error instanceof ErrorEvent) 
      errorMessage = `An error occurred: $err.error.message`;
     else 
      errorMessage = `Server returned code: $err.status, error message is: $err.message`
    
    console.error(errorMessage);
    return throwError(errorMessage);
  ;

我写的测试如下:

import  HttpClientTestingModule, HttpTestingController  from "@angular/common/http/testing";
import  Certificate  from "src/app/models";
import  APICONFIG  from "src/app/config/api.config";
import  TestBed  from "@angular/core/testing";
import  CertificateService  from "../certificate.service";


describe('CertificateService', ()=>
    let httpTestingController: HttpTestingController;
    const APIURL = `$APICONFIG.base/education/certificate`;

    let certificatesJson = 
    [
        
            "id": "1",
            "model": 
                "institute": "Institute",
                "name": "Certificaat",
                "description": "Description",
                "achievementDate": "2021-12-14T16:27:02.000+00:00",
                "expirationDate": "2021-12-14T16:27:08.000+00:00",
                "url": "url"
            
        ,
        
            "id": "2",
            "model": 
                "institute": "Institute2",
                "name": "Certificaat2",
                "description": "Description2",
                "achievementDate": "2021-12-14T16:27:02.000+00:00",
                "expirationDate": "2021-12-14T16:27:08.000+00:00",
                "url": "url2"
            
        
    ]

    let certificatesParsed: Certificate[] = [id: "1", institute: "Institute", name: "Certificaat",  description: "Description", achievementDate: "2021-12-14T15:59:01.000+00:00", expirationDate: "2021-12-14T15:59:11.000+00:00", url: "url",
                                            id: "2", name: "Certificaat", institute: "Institute", description: "Description", achievementDate: "2021-12-14T15:59:01.000+00:00", expirationDate: "2021-12-14T15:59:11.000+00:00", url: "url"];
    

    beforeEach(()=>
        TestBed.configureTestingModule(
            imports: [HttpClientTestingModule],
            providers: [CertificateService]
        );
        httpTestingController = TestBed.inject(HttpTestingController);
    );

    describe('getAllUserCertificates', () =>
        it('should return all certificate objects from user', () => 
            let id = "1";
            const certificateService = TestBed.inject(CertificateService);
            certificateService.getAllUserCertificates(id).subscribe(certificates => 
                expect(certificates.length).toBe(2);
            );

            const req = httpTestingController.expectOne(`$APIURL/$id`);
            expect(req.request.method).toBe('GET');
            
            req.flush(certificatesJson);
            httpTestingController.verify();
        );

        it('should create new observable if id is null', () => 
            let id = "";
            const certificateService = TestBed.inject(CertificateService);
            const spy = spyOn(certificateService, 'getAllUserCertificates').and.callThrough();
            certificateService.getAllUserCertificates(id).subscribe();

            expect(spy.calls.count()).toEqual(1);
     
            httpTestingController.verify();
        );
    )

    describe('saveUserCertificates', () =>
        it('should post correct certificate objects', () => 
            
            const certificateService = TestBed.inject(CertificateService);
            certificateService.saveUserCertificates(certificatesParsed).subscribe(certificates => 
                expect(certificates.length).toBe(2);
            );

            const req = httpTestingController.expectOne(APIURL);
            expect(req.request.method).toBe('POST');
            
            req.flush(certificatesJson);
            httpTestingController.verify();
        );
    )

    describe('parseCertificateData', ()=>
        it('should parse data into certificate objects', () => 

            const certificateService = TestBed.inject(CertificateService);
            let certificates: Certificate[] = certificateService.parseCertificateData(certificatesJson);
            
            expect(certificates.length).toBe(2);
            expect(certificates[0].institute).toBe("Institute");
            expect(certificates[0].name).toBe("Certificaat");
            expect(certificates[0].description).toBe("Description");
            expect(certificates[0].url).toBe("url");
            expect(certificates[1].institute).toBe("Institute2");
            expect(certificates[1].name).toBe("Certificaat2");
            expect(certificates[1].description).toBe("Description2");
            expect(certificates[1].url).toBe("url2");
        );
    )
    
    describe('handleError', ()=>
        it('should handle bad request', () => 
            let id = "11";
            let response: any;
            let errResponse: any;
            const mockErrorResponse =  status: 400, statusText: 'Bad Request' ;
            const data = `Server returned code: 400, error message is: Http failure response for $APIURL/11: 400 Bad Request`;
            const certificateService = TestBed.inject(CertificateService);

            certificateService.getAllUserCertificates('11').subscribe(res => response = res, err => errResponse = err);
            httpTestingController.expectOne(`$APIURL/$id`).flush(data, mockErrorResponse);
            expect(errResponse).toBe(data);
        );

        it('should handle event error', () => 
            let id = "11";
            let response: any;
            let errResponse: any;
            const errorInitEvent: ErrorEventInit = 
                error : new Error('Error'),
                message : 'Error'
            ;

            const mockErrorResponse =  status: 400, statusText: 'Bad Request';
            const data = new ErrorEvent('MyErrEventType', errorInitEvent);
            
            const certificateService = TestBed.inject(CertificateService);

            certificateService.getAllUserCertificates('11').subscribe(res => response = res, err => errResponse = err);
            httpTestingController.expectOne(`$APIURL/$id`).flush(data, mockErrorResponse);
            expect(errResponse).toBe('An error occurred: Error');
        );
    )

)

我的 karma 的 console.log 目前充满了错误:

发生错误:错误 和 服务器返回代码:400,错误信息是: Http failure response for URL 400 Bad Request。 感觉就像我使用的是真实的服务而不是模拟的服务。是否有可能告诉我这样做的正确方法是什么?

感谢您的帮助,祝您周末愉快。

【问题讨论】:

嗨 Jens,我不知道它为什么会发出实际的 API 请求。看代码,不应该。话虽如此,我会尝试 Chaka15 的建议,在let httpTestingController: HttpTestingController; 之后,有let certificateService: CertificateService;,然后在httpTestingController = TestBed.inject(HttpTestingController); 之后有certificateService = TestBed.inject(CertificateService);,然后使用certificateService,不要每次都得到一个新实例。 【参考方案1】:

不完全确定导致测试失败的原因,但请尝试以下方法:

httpTestingController = TestBed.inject(HttpTestingController); 之后的第一个beforeEach 块内放置此行certificateService = TestBed.inject(CertificateService); 。 在 c 的顶部声明 certificateService

现在您不需要在每个 it 块中使用相同的起始代码 const certificateService = TestBed.inject(CertificateService);

还在beforeEach 之后定义“全局”afterEach 块,您可以在其中执行以下操作: httpTestingController.verify();(又是 DRY 原则)。

如果这有帮助,请告诉我。

【讨论】:

以上是关于Angular如何测试服务方法?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用注入服务测试 Angular 1.6 组件?

如何在 Angular 组件中模拟服务功能以进行单元测试

如何使用 Apollo Angular 测试查询、变异、订阅服务?

Angular 小吃店服务茉莉花测试

使用jasmine.SpyObj-Angular / Jasmine测试服务属性上的可观察对象

在 Jest 测试框架中访问 Angular 服务构造函数