Angular Testing a service using HttpClient is not

2020-07-27 02:38发布

问题:

I'm working on a Angular 5 Project, it's nothing serious. Though I've been out of the Angular2+ business since early 2.1/2.2.

So I've got this Service which calls a public API, however my test keeps failing with: Error: Expected one matching request for criteria "Match URL: http://api.icndb.com/jokes/random/10", found none.

code:

fact.service.spec.ts

import {HttpClientTestingModule, HttpTestingController} from "@angular/common/http/testing";
import {TestBed, inject} from "@angular/core/testing";
import {FactService} from "./fact.service";
import {Fact} from "../models/fact";
import {FactHttpResponse} from "../models/factHttpResponse";

describe("FactService", () => {
    let factService: FactService;
    let httpMock: HttpTestingController;

    beforeEach(() => {
        TestBed.configureTestingModule({
            imports: [HttpClientTestingModule],
            providers: [FactService],
        });
        httpMock = TestBed.get(HttpTestingController);
        factService =  TestBed.get(FactService);
    });

    // TODO: Something is going wrong with this test get some help online
    it("Should be able to retrieve Facts in subscription when calling loadFacts", (done) => {
    const factList: Fact[] = [
        {id: 1, joke: "a"},
        {id: 2, joke: "a"},
    ];

    const factResponse: FactHttpResponse = { value: factList};

    factService.subscribe(val => {
        expect(val).toEqual(factList);
        done();
    });

    const mockRequest = httpMock
        .expectOne(factService.CHUCK_NORRIS_API.replace("{count}", "10"));
    expect(mockRequest.request.method).toEqual('GET');

    factService.loadFacts();

    mockRequest.flush(factResponse);
});

afterEach(inject([HttpTestingController], (httpMock: HttpTestingController) => {
    httpMock.verify();
}));
});

fact.service.ts

 import { HttpClient } from "@angular/common/http";
 import { Injectable } from "@angular/core";
 import { Subject } from "rxjs/Subject";

 import {Fact} from "../models/fact";
 import {FactHttpResponse} from "../models/factHttpResponse";

@Injectable()
export class FactService {

// public for testing purposes
public readonly CHUCK_NORRIS_API = "http://api.icndb.com/jokes/random/{count}";
private factSubject = new Subject();
private facts: Fact[] = [];
private favorites: Fact[] = [];

constructor(private http: HttpClient) { }

public subscribe(callback: (value: Fact[]) => void) {
    return this.factSubject.subscribe(callback);
}

public loadFacts(count = 10) {
    this.http.get<FactHttpResponse>(this.CHUCK_NORRIS_API.replace("{count}", count.toString()))
        .subscribe(data => {
           if (data.value) {
               this.facts = data.value
                   .map(f => {
                       f.favorite = !!this.favorites.find(fav => fav.id === f.id);
                       return f;
                   });
               this.factSubject.next(this.facts);
           }
        });
}
}

The code actually works, but I can't seem to test it :-(. If I remove the verifyOne it actually complains about an open request to the exact same url mentioned in error.

回答1:

Since it is your method factService.loadFacts() that is generating the HTTP request, you have the last three pieces of your test out of order. It should be (with comments):

describe(..., () => {
    it(..., () => {
        ...

        // queue up the http request in the mockHttp request queue
        factService.loadFacts();

        // verify that there is now one (and only one) request queued up
        const mockRequest = httpMock
            .expectOne(factService.CHUCK_NORRIS_API.replace("{count}", "10"));
        expect(mockRequest.request.method).toEqual('GET');

        // satisfy the pending request in the mockHttp request queue
        mockRequest.flush(factResponse);
    });

    // Bonus tip: you already have mockHttp in the describe() scope.  Simplify:
    afterEach(() => {
        // verify there are no unsatisfied requests in the mockHttp queue
        httpMock.verify();
    });
});