1

I have a course service that gets its data from a backend server. I'm trying to write a spec to the service and components that request data from it. I only have get requests. I have had a lot of failures and I didn't find anything on google answers.

these are the interfaces

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

Course Service Errors:

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

Course-List Component Errors:

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

should create
[object ErrorEvent] thrown

This are the components and the specs:

// ------ 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 Answers 2

3

In course service spec remove HttpTestingController from imports array, because it is not module:

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

And as well try to use correct object notations:

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[];
  });

You can see, that was typo in object notation, was: segments = [ , which should be segments: [. Please, fix in all the places

Over here, as well I see typo:

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

UPDATE

The error Error: Expected no open requests, found 1: GET localhost:3000/courses.json appears, because you are expecting requests in afterEach block:

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

To overcome that issue, you can use httpTestingController to simulate request call : https://angular.io/api/common/http/testing/HttpTestingController

I can help you with first one, and others you will have to do yourself: In course service spec:

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

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

For the following assertion use the same approach.

Sign up to request clarification or add additional context in comments.

2 Comments

thanks! now it displays another error instead: ReferenceError: segments is not defined. I wanted to say that there's no problem with the data, it pass correctly and there's no errors in the implement
thanks! now I have other error instead: Error: Expected no open requests, found 1: GET localhost:3000/courses.json
1

I tried to use the real data instead use a mock data by configure expectedCourses like that: expectedCourses = courseService.getCourses(); and expectedCourse like that: expectedCourse = courseService.getCourse(3); and most of the error are gone so this is what I did.

thanks for everyone who answered me!

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.