Angular 6:使用httpclient请求错误测试服务

时间:2018-08-27 12:38:42

标签: angular unit-testing httpclient karma-jasmine specifications

我有一个课程服务,可以从后端服务器获取其数据。我正在尝试为服务和向其请求数据的组件编写规范。我只有请求。我失败了很多,但在Google答案中什么都没找到。

这些是界面

export interface ICourse {
  id: number;
  title: string;
  author: string;
  segments: ISegment[];
}

export interface ISegment {
  id: number;
  unit_id: number;
  unit_title: string;
  name: string;
  type: string;
  data: string;
  questions: IQuestion[];
}

export interface IQuestion {
  id: number;
  question: string;
  answer1: string;
  answer2: string;
  answer3: string;
  answer4: string;
  correct: number;
}

课程服务错误:

CourseService getCourses should return all courses
Error: Expected no open requests, found 1: GET http://localhost:3000/courses.json
TypeError: Cannot read property 'getCourses' of undefined
TypeError: Cannot read property 'verify' of undefined

课程列表组件错误:

getCourses should return an Observable<ICourse[]>
Expected undefined to be truthy.

should create
[object ErrorEvent] thrown

这是组件和规格:

// ------ course.service ------ //

import { Injectable } from '@angular/core';
import { HttpClient, HttpErrorResponse, HttpHeaders } from '@angular/common/http';
import { BehaviorSubject } from 'rxjs';

import { Observable, throwError } from 'rxjs';
import { catchError, groupBy } from 'rxjs/operators';

import { ICourse } from './course';
import { HttpErrorHandler, HandleError } from './http-error-handler.service'

// Inject Data from Rails app to Angular app
@Injectable()
export class CourseService {

  // JSON url to get data from
  private url = 'http://localhost:3000/courses';
  private courseUrl = 'http://localhost:3000/courses.json';


  constructor(
    private http: HttpClient) { }

  // // Handle Any Kind of Errors
  private handleError(error: HttpErrorResponse) {

    // A client-side or network error occured. Handle it accordingly.
    if (error.error instanceof ErrorEvent) {
      console.error('An error occured:', error.error.message);
    }

    // The backend returned an unsuccessful response code.
    // The response body may contain clues as to what went wrong.
    else {
      console.error(
        'Backend returned code ${error.status}, ' +
        'body was ${error.error}');
    }

    // return an Observable with a user-facing error error message
    return throwError(
      'Something bad happend; please try again later.');
  }

  // Get All Courses from Rails API App
  getCourses(): Observable<ICourse[]> {
  const coursesUrl = `${this.url}` + '.json';

  return this.http.get<ICourse[]>(coursesUrl)
      .pipe(catchError(this.handleError));
  }

  // Get Single Course by id. will 404 if id not found
  getCourse(id: number): Observable<ICourse> {
    const detailUrl = `${this.url}/${id}` + '.json';
    return this.http.get<ICourse>(detailUrl)
        .pipe(catchError(this.handleError));
  }
}

// ------ course.service.spec ------ //

import { HttpClient, HttpResponse } from '@angular/common/http';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { TestBed, async, getTestBed, ComponentFixture, inject, tick, fakeAsync } from '@angular/core/testing';
import { DebugElemet } from '@angular/core';
import { By } from '@angular/platform-browser';

import { CourseService } from './course.service';
import { ICourse } from './course';

describe('CourseService', () => {
  let httpClient: HttpClient;
  let httpTestingController: HttpTestingController;
  let courseService: CourseService;


  // before each test, default value and delete old test
  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule],
      providers: [CourseService]
    });

    // Inject the http, test controller, and service-under-test
    // as they will be referenced by each test.
    httpClient = TestBed.get(HttpClient);
    httpTestingController = TestBed.get(HttpTestingController);
    courseService = TestBed.get(CourseService);
  });

  // After every test, assert that there are no more pending requests.
  afterEach(() => {
    httpTestingController.verify();
  });

  // ----------- CourseService method tests begin ----------- //

  // Test getCourses()
  describe('getCourses', () => {
      // Mock Data to test the service
      let expectedCourses: ICourse[];

      beforeEach(() => {
        courseService = TestBed.get(CourseService);
        //expectedCourses = courseService.getCourses();

        expectedCourses = [
          { id: 1, title: "Angular is Fun", author: "Son Goku", segments: [
            { id: 1, unit_id: 1, unit_title: "Components", name: "Lesson 1: Create Components", type: "Video", data: "www.hellokitty.com/angular1.flv" },
            { id: 2, unit_id: 1, unit_title: "Components", name: "Lesson 2: Decorators", type: "Video", data: "www.hellokitty.com/angular2.flv" },
            { id: 3, unit_id: 1, unit_title: "Components", name: "Lesson 3: Life Cycle", type: "Video", data: "www.hellokitty.com/angular3.flv" } ]
          },
          { id: 2, title: "Ruby on Rails", author: "Monokuma", segments: [
            { id: 4, unit_id: 1, unit_title: "Introduction", name: "Lesson 1: Rails Console", type: "Video", data: "www.sdr2.com/rails1.flv" },
            { id: 5, unit_id: 2, unit_title: "Introduction", name: "Lesson 1: Gems", type: "Video", data: "www.sdr2.com/rails2.flv" } ]
          },
          { id: 3, title: "Java", author: "Hououin Kyouma", segments: [
            { id: 6, unit_id: 1, unit_title: "Data Structures", name: "Lesson 1: Node", type: "Video", data: "www.deathnote.com/java1.flv" },
            { id: 7, unit_id: 1, unit_title: "Data Structures", name: "Lesson 2: Stack", type: "Video", data: "www.deathnote.com/java2.flv" },
            { id: 8, unit_id: 1, unit_title: "Data Structures", name: "Lesson 3: List", type: "Video", data: "www.deathnote.com/java3.flv" }]
          }
        ] as ICourse[];
      });

      // Test getCoures()
      it('should return all courses', () => {
        courseService.getCourses().subscribe(
          courses => expect(courses).toEqual(expectedCourses))
      });

  });

  // Test getCourse(id)
  describe('getCourse', () => {
    // Mock Data to test the service
    let expectedCourse: ICourse;

    beforeEach(() => {
      courseService = TestBed.get(CourseService);

      expectedCourse = { id: 3, title: "Java", author: "Hououin Kyouma", segments = [
                        { id: 6, unit_id: 1, unit_title: "Data Structures", name: "Lesson 1: Node", type: "Video", data: "www.deathnote.com/java1.flv" },
                        { id: 7, unit_id: 1, unit_title: "Data Structures", name: "Lesson 2: Stack", type: "Video", data: "www.deathnote.com/java2.flv" },
                        { id: 8, unit_id: 1, unit_title: "Data Structures", name: "Lesson 3: List", type: "Video", data: "www.deathnote.com/java3.flv" }]
                      } as ICourse;
    });

    it('should return course by id', () => {
        courseService.getCourse(3).subscribe(
          courses => expect(course).toEqual(expectedCourses[2]))
    });
  });

});

// ----------- course-list.component ----------- //

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router, Routes } from '@angular/router';

import { ICourse } from '../course';
import { CourseService } from '../course.service';


// Course-list decorator
@Component({
  selector: 'lg-course-list',
  templateUrl: './course-list.component.html',
  styleUrls: ['./course-list.component.sass']
})

export class CourseListComponent implements OnInit {
  pageTitle = "Labguru Academy";
  courses: ICourse[] =[];
  errorMessage: string;

  constructor(private courseService: CourseService,
        private route: ActivatedRoute,
        private router: Router) {
  }

  // Get list of courses from service
  getCourseList() {
    this.courseService.getCourses()
      .subscribe(
        courses => this.courses = courses,
        errorMessage => this.errorMessage = <any>Error
      );
  }

  // On start of the life cycle
  ngOnInit() {
    this.getCourseList();
  }

}

// ----------- course-list.component.spec ----------- //

import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { DebugElement } from '@angular/core';
import { By } from '@angular/platform-browser';

import { CourseService } from '../course.service';
import { CourseListComponent } from './course-list.component';
import { ICourse } from './course';

describe('CourseListComponent', () => {
  let component: CourseListComponent;
  let service: CourseService;
  let fixture: ComponentFixture<CourseListComponent>;
  let de: DebugElement;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [ RouterTestingModule, HttpClientTestingModule ],
      declarations: [ CourseListComponent ],
      providers: [ CourseService ]
    })
    .compileComponents();
  }));

  beforeEach(() => {
    service = new CourseService();
    component = new CourseListComponent(service);
  });


  // Check the title of the course-list page
  it(`should have as title 'Labguru Academy'`, async(() => {
    fixture = TestBed.createComponent(CourseListComponent);
    component = fixture.debugElement.componentInstance;
    expect(component.pageTitle).toContain('Labguru Academy');
  }));

  // Test getCourses()
  describe('getCourses', () => {
      it('should return an Observable<ICourse[]>', () => {
        fixture = TestBed.createComponent(CourseListComponent);
        component = fixture.debugElement.componentInstance;
        expect(component.getCourseList()).toBeTruthy();
      });
  });


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

2 个答案:

答案 0 :(得分:3)

当然,服务规范会从导入数组中删除HttpTestingController,因为它不是模块:

TestBed.configureTestingModule({
  imports: [HttpClientTestingModule],
  providers: [CourseService]
});

以及尝试使用正确的对象符号:

expectedCourses = [
      { id: 1, title: "Angular is Fun", author: "Son Goku", segments: [
        { id: 1, unit_id: 1, unit_title: "Components", name: "Lesson 1: Create Components", type: "Video", data: "www.hellokitty.com/angular1.flv" },
        { id: 2, unit_id: 1, unit_title: "Components", name: "Lesson 2: Decorators", type: "Video", data: "www.hellokitty.com/angular2.flv" },
        { id: 3, unit_id: 1, unit_title: "Components", name: "Lesson 3: Life Cycle", type: "Video", data: "www.hellokitty.com/angular3.flv" } ]
      },
      { id: 2, title: "Ruby on Rails", author: "Monokuma", segments: [
        { id: 4, unit_id: 1, unit_title: "Introduction", name: "Lesson 1: Rails Console", type: "Video", data: "www.sdr2.com/rails1.flv" },
        { id: 5, unit_id: 2, unit_title: "Introduction", name: "Lesson 1: Gems", type: "Video", data: "www.sdr2.com/rails2.flv" } ]
      },
      { id: 3, title: "Java", author: "Hououin Kyouma", segments: [
        { id: 6, unit_id: 1, unit_title: "Data Structures", name: "Lesson 1: Node", type: "Video", data: "www.deathnote.com/java1.flv" },
        { id: 7, unit_id: 1, unit_title: "Data Structures", name: "Lesson 2: Stack", type: "Video", data: "www.deathnote.com/java2.flv" },
        { id: 8, unit_id: 1, unit_title: "Data Structures", name: "Lesson 3: List", type: "Video", data: "www.deathnote.com/java3.flv" }]
      }
    ] as ICourse[];
  });

您可以看到,这是对象符号中的错别字,是:segments = [,应该是segments:[。请在所有地方修复

在这里,我也看到错字:

  // Test getCoures()
  it('should return all courses', () => {
    courseService.getCourses().subscribe(
      //was courses => expect(courses).toEqual(expectedCourses]))
      //should be
      courses => expect(courses).toEqual(expectedCourses)
  )
  });

更新

错误错误:预期没有打开的请求,发现1:出现GET localhost:3000 / courses.json,因为您希望在afterEach块中请求:

 // After every test, assert that there are no more pending requests.
  afterEach(() => {
    httpTestingController.verify();
  });

要解决该问题,您可以使用httpTestingController模拟请求调用:https://angular.io/api/common/http/testing/HttpTestingController

我可以为您提供第一个帮助,而其他一些则需要您自己做: 当然是服务规格:

it('should return all courses', (done) => {
     courseService.getCourses().subscribe(
       courses => { 
           expect(courses).toEqual(expectedCourses));
           done();
       }

     const request = httpTestingController.expectOne('/courses.json');
     request.flush(expectedCourses);
});

对于以下断言,请使用相同的方法。

答案 1 :(得分:1)

我试图通过像这样配置ExpectedCourses来使用真实数据而不是模拟数据:ExpectedCourses = courseService.getCourses();和ExpectedCourse一样:ExpectedCourse = courseService.getCourse(3);并且大多数错误都消失了,所以这就是我所做的。

感谢所有回答我的人!