zoukankan      html  css  js  c++  java
  • [Angular 8 Unit Testing] Angular 8 Unit Testing -- service

    How to test a service

    The service we want to test against:

    import {Injectable} from '@angular/core';
    import {LoggerService} from './logger.service';
    
    
    @Injectable({
      providedIn: 'root'
    })
    export class CalculatorService {
    
    
      constructor(private logger: LoggerService) {
    
      }
    
      add(n1: number, n2:number) {
        this.logger.log("Addition operation called");
        return n1 + n2;
      }
    
      subtract(n1: number, n2:number) {
        this.logger.log("Subtraction operation called");
        return n1 - n2;
      }
    
    
    }
    

     For this service, we want to test two things:

    1. add / subtract functions should return the correct values.

    2. The logger service should be called only once for each method.

    Using Spy to mock a service

    For logger service, we don't want to call the real service, it might trigger side effects, for example, send analytics request. We want to mock a logger service:

        beforeEach(()=> {
            loggerSpy = jasmine.createSpyObj('LoggerService', ['log'])
            TestBed.configureTestingModule({
                providers: [
                    CalculatorService,
                    // If you want to use spy, you have to use provide + useValue syntax, otherwise, Angular will init an actual
                    // implementation of LoggerService.
                    {provide: LoggerService, useValue: loggerSpy}
                ]
            })
            calculator = TestBed.get(CalculatorService)
        });

    Full test code:

    import {CalculatorService} from './calculator.service';
    import {LoggerService} from './logger.service';
    import {TestBed} from '@angular/core/testing';
    
    // fdescribe
    describe('CalculatorService', () => {
    
        let calculator: CalculatorService,
            loggerSpy: any;
    
        beforeEach(()=> {
            loggerSpy = jasmine.createSpyObj('LoggerService', ['log'])
            TestBed.configureTestingModule({
                providers: [
                    CalculatorService,
                    // If you want to use spy, you have to use provide + useValue syntax, otherwise, Angular will init an actual
                    // implementation of LoggerService.
                    {provide: LoggerService, useValue: loggerSpy}
                ]
            })
            calculator = TestBed.get(CalculatorService)
        });
    
        // fit
        it('should add two numbers', () => {
            const result = calculator.add(2, 2);
            expect(result).toBe(4);
            expect(loggerSpy.log).toHaveBeenCalledTimes(1);
        });
    
        it('should subtract two numbers', () => {
    
            const result = calculator.subtract(2, 2);
            expect(result).toBe(0, "unexpected subtraction result");
            expect(loggerSpy.log).toHaveBeenCalledTimes(1);
        });
    });

    Testing a service with HTTP request

    The service we want to test against:

    import { Injectable } from "@angular/core";
    import { HttpClient, HttpParams } from "@angular/common/http";
    import { Observable } from "rxjs";
    import { map } from "rxjs/operators";
    import { Lesson } from "../model/lesson";
    import { Course } from "../model/course";
    
    @Injectable()
    export class CoursesService {
      constructor(private http: HttpClient) {}
    
      findCourseById(courseId: number): Observable<Course> {
        return this.http.get<Course>(`/api/courses/${courseId}`);
      }
    
      findAllCourses(): Observable<Course[]> {
        return this.http.get("/api/courses").pipe(map(res => res["payload"]));
      }
    
      saveCourse(courseId: number, changes: Partial<Course>): Observable<Course> {
        return this.http.put<Course>(`/api/courses/${courseId}`, changes);
      }
    
      findLessons(
        courseId: number,
        filter = "",
        sortOrder = "asc",
        pageNumber = 0,
        pageSize = 3
      ): Observable<Lesson[]> {
        return this.http
          .get("/api/lessons", {
            params: new HttpParams()
              .set("courseId", courseId.toString())
              .set("filter", filter)
              .set("sortOrder", sortOrder)
              .set("pageNumber", pageNumber.toString())
              .set("pageSize", pageSize.toString())
          })
          .pipe(map(res => res["payload"]));
      }
    }
    

      

    For this service, we also don't want to issue a real http request, therefore we also need to mock a HTTP service, Angular provides HttpClientTestingModule for us. We can use the HttpTestingController.

      let coursesService: CoursesService,
        httpTestingController: HttpTestingController;
    
      beforeEach(() => {
        TestBed.configureTestingModule({
          imports: [HttpClientTestingModule],
          providers: [CoursesService]
        });
    
        (coursesService = TestBed.get(CoursesService)),
          (httpTestingController = TestBed.get(HttpTestingController));
      });

    Example code:

      it("should retrieve all courses", () => {
        coursesService.findAllCourses().subscribe(courses => {
          expect(courses).toBeTruthy("No courses returned");
    
          expect(courses.length).toBe(12, "incorrect number of courses");
    
          const course = courses.find(course => course.id == 12);
    
          expect(course.titles.description).toBe("Angular Testing Course");
        });
        // expect only one request
        const req = httpTestingController.expectOne("/api/courses");
        // check the request should be the GET request
        expect(req.request.method).toEqual("GET");
        // issue the mock http request and return the response
        req.flush({ payload: Object.values(COURSES) });
      });

    Testing a error HTTP request

    Calling 'fail' method from Jasmine in the success block, which means "This test should in Error block, but landed on Success block, so we consider the test is failed."

      it("should give an error if save course fails", () => {
        const changes: Partial<Course> = {
          titles: { description: "Testing Course" }
        };
        coursesService.saveCourse(12, changes).subscribe(
          () => fail("save course operation should have failed"),
          (error: HttpErrorResponse) => {
            expect(error.status).toBe(500);
          }
        );
    
        const req = httpTestingController.expectOne("/api/courses/12");
        expect(req.request.body.titles.description).toEqual("Testing Course");
        expect(req.request.method).toBe("PUT");
        req.flush("save course fail", {
          status: 500,
          statusText: "Internal server error"
        });
      });

    Testing HTTP params

    Example Code:

      it("should find a list of lessons", () => {
        coursesService.findLessons(12).subscribe(lessons => {
          expect(lessons).toBeTruthy();
          expect(lessons.length).toBe(3);
        });
        // expectOne can also take function
        const req = httpTestingController.expectOne(
          req => req.url == "/api/lessons"
        );
        expect(req.request.method).toEqual("GET");
        expect(req.request.params.get("courseId")).toEqual("12");
        expect(req.request.params.get("filter")).toEqual("");
        expect(req.request.params.get("sortOrder")).toEqual("asc");
        expect(req.request.params.get("pageNumber")).toEqual("0");
        expect(req.request.params.get("pageSize")).toEqual("3");
    
        req.flush({
          payload: findLessonsForCourse(12).slice(0, 3)
        });
      });

    --

    Full testing code:

    import { CoursesService } from "./courses.service";
    import { TestBed } from "@angular/core/testing";
    import {
      HttpClientTestingModule,
      HttpTestingController
    } from "@angular/common/http/testing";
    import { COURSES, findLessonsForCourse } from "../../../../server/db-data";
    import { Course } from "../model/course";
    import { HttpErrorResponse } from "@angular/common/http";
    
    fdescribe("CoursesService", () => {
      let coursesService: CoursesService,
        httpTestingController: HttpTestingController;
    
      beforeEach(() => {
        TestBed.configureTestingModule({
          imports: [HttpClientTestingModule],
          providers: [CoursesService]
        });
    
        (coursesService = TestBed.get(CoursesService)),
          (httpTestingController = TestBed.get(HttpTestingController));
      });
    
      it("should retrieve all courses", () => {
        coursesService.findAllCourses().subscribe(courses => {
          expect(courses).toBeTruthy("No courses returned");
    
          expect(courses.length).toBe(12, "incorrect number of courses");
    
          const course = courses.find(course => course.id == 12);
    
          expect(course.titles.description).toBe("Angular Testing Course");
        });
    
        const req = httpTestingController.expectOne("/api/courses");
    
        expect(req.request.method).toEqual("GET");
    
        req.flush({ payload: Object.values(COURSES) });
      });
    
      it("should save the course data", () => {
        const changes: Partial<Course> = {
          titles: { description: "Testing Course" }
        };
    
        coursesService.saveCourse(12, changes).subscribe(course => {
          expect(course.id).toEqual(12);
        });
    
        const req = httpTestingController.expectOne("/api/courses/12");
        expect(req.request.body.titles.description).toEqual("Testing Course");
        expect(req.request.method).toBe("PUT");
        req.flush({
          ...COURSES[12],
          ...changes
        });
      });
    
      it("should give an error if save course fails", () => {
        const changes: Partial<Course> = {
          titles: { description: "Testing Course" }
        };
        coursesService.saveCourse(12, changes).subscribe(
          () => fail("save course operation should have failed"),
          (error: HttpErrorResponse) => {
            expect(error.status).toBe(500);
          }
        );
    
        const req = httpTestingController.expectOne("/api/courses/12");
        expect(req.request.body.titles.description).toEqual("Testing Course");
        expect(req.request.method).toBe("PUT");
        req.flush("save course fail", {
          status: 500,
          statusText: "Internal server error"
        });
      });
    
      it("should find a list of lessons", () => {
        coursesService.findLessons(12).subscribe(lessons => {
          expect(lessons).toBeTruthy();
          expect(lessons.length).toBe(3);
        });
    
        const req = httpTestingController.expectOne(
          req => req.url == "/api/lessons"
        );
        expect(req.request.method).toEqual("GET");
        expect(req.request.params.get("courseId")).toEqual("12");
        expect(req.request.params.get("filter")).toEqual("");
        expect(req.request.params.get("sortOrder")).toEqual("asc");
        expect(req.request.params.get("pageNumber")).toEqual("0");
        expect(req.request.params.get("pageSize")).toEqual("3");
    
        req.flush({
          payload: findLessonsForCourse(12).slice(0, 3)
        });
      });
    
      afterEach(() => {
        // make sure the no extra http request are called
        httpTestingController.verify();
      });
    });
  • 相关阅读:
    【转载】S5PV210 三星官方原理图(包含核心板和底板)
    关于飞凌技术支持更改通知
    【收集】几个gooogleman嵌入式联盟比较好的帖子
    分析我的OV3640 打开软件立即导致PDA死机的原因
    【喜讯】嘿嘿,Real6410/TE6410/OK6410 支持jlink V8+RVDS2.2 仿真调试了
    【转载】三星A8 S5pV210 硬件设计指南S5PV210_Hardware Design Guide_Rev1.0
    【爆料】公布一个经典6410 原理图(orcad)+PCB(candence)图—— real6410 PCB 大全(核心板+底板)
    【呜呼】大学生烧毕业证书谁的错?!
    【转载】三星A8 S5pV210 硬件设计指南S5PV210_Hardware Design Guide_Rev1.0
    【转载】2440的GPIO模拟IIC程序
  • 原文地址:https://www.cnblogs.com/Answer1215/p/12299646.html
Copyright © 2011-2022 走看看