【问题标题】:Testing Service Angular测试服务角
【发布时间】:2019-04-25 13:34:24
【问题描述】:

我遇到了一个问题,我不知道如何解决它,因为我对前面的测试非常陌生。

现在我正在测试具有此代码的服务:

import { Injectable } from '@angular/core';
import { EndpointLocatorService } from './endpointLocator.service';
import { Http, Headers } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';

@Injectable()
export class CancelAppService {

    constructor(private readonly http: Http,
        private readonly _endPointLocator: EndpointLocatorService) { }

    getScopeSignature(signatureToken: string) {
        const url = this._endPointLocator.locate('cancelApp');
        const language = sessionStorage.getItem('languageSession');
        const headers = new Headers({
            'Content-Type': 'application/json',
            'Accept-Language': language,
            'X-B3-TraceId': sessionStorage.getItem('requestID')
        });
        const body = JSON.stringify({ 'signatureToken': signatureToken });
        return this.http.post(url, body, { headers }).map(data => {
            return data.json();
        }).catch((error: any) => {
            return Observable.throw(new Error(error.status));
        });
    }
}

测试文件包含以下内容:

import { TestBed } from '@angular/core/testing';
import { CancelAppService } from './cancelApp.service';
import { HttpModule } from '@angular/http';
import { EndpointLocatorService } from './endpointLocator.service';
import { AppConfig } from '../app.config';
import 'jasmine';

fdescribe('CancelAppService', () => {
    let cancelService: CancelAppService; // Add this
    let endLocator: EndpointLocatorService;
    const mockData = {
        "signature_token": "returnedToken"
    };
    const body = JSON.stringify({ 'signatureToken': 'givenToken' });
    beforeEach(() => {
        TestBed.configureTestingModule({
            imports: [
                HttpModule
            ],
            providers: [
                CancelAppService,
                AppConfig,
                EndpointLocatorService
            ]
        });
        cancelService = TestBed.get(CancelAppService); // Add this
        endLocator = TestBed.get(EndpointLocatorService);
    });

    it('should be created', () => { // Remove inject()
        expect(cancelService).toBeDefined();
    });

    it('should call the service', () => {
        spyOn(endLocator, 'locate').and.returnValue('someUrl');
        spyOn(cancelService, 'getScopeSignature').and.callThrough();
        cancelService.getScopeSignature(body);
        expect(cancelService.getScopeSignature).toHaveBeenCalled();
    });

    it('should return a object', () => {
        (done: DoneFn) => {
            spyOn(endLocator, 'locate').and.returnValue('someUrl');
            spyOn(cancelService, 'getScopeSignature').and.returnValue(mockData);
            cancelService.getScopeSignature(body).subscribe(data => {
                expect(data).toEqual(mockData);
                done();
            });
        }
    });
});

问题是,当我尝试测试返回的数据映射时,我看起来像是一个成功的测试,但覆盖率说我没有覆盖地图的线条,然后捕获。

知道我做错了什么吗?以及如何解决?

非常感谢!!

【问题讨论】:

  • 你的 cli 版本是多少
  • "@angular/cli": "^7.2.2", "@angular/compiler-cli": "^7.2.14",
  • 你在哪里伪造调用,让它去 catch 块?
  • @JuanMendes 我不在这里上传那段代码,因为我更喜欢知道如何首先测试 .map() 部分并尝试弄清楚如何进行捕获:)。

标签: javascript angular testing jasmine karma-runner


【解决方案1】:

您正在模拟正在测试的服务,您应该模拟您的服务 EndpointLocatorService 甚至可能是 HttpClient 正在使用的服务,以尽量减少模拟。您可以通过单步执行代码来验证这一点。

让我在代码中解释为什么这些行没有被命中。

// In this example, the lines are hit, but after the test exited.
it('should call the service', () => {
    spyOn(endLocator, 'locate').and.returnValue('someUrl');
    spyOn(cancelService, 'getScopeSignature').and.callThrough();
    // Here you are calling a method yourself and checking that it was
    // called, it doesn't make sense, this test should make sure
    // You should check that your endLocator was called.
    // Note that because you are not waiting for the promise to resolve,
    // The test finishes before either callback is handled.
    cancelService.getScopeSignature(body);
    expect(cancelService.getScopeSignature).toHaveBeenCalled();
});


// In this example the lines in your test are never hit because you
// mocked it
it('should return a object', () => {
    (done: DoneFn) => {
        spyOn(endLocator, 'locate').and.returnValue('someUrl');
        // Here you are mocking getScopeSignature completely so it doesn't
        // Run any of your lines that you are trying to test
        // It just tests that you mocked it correctly
        spyOn(cancelService, 'getScopeSignature').and.returnValue(mockData);
        cancelService.getScopeSignature(body).subscribe(data => {
            expect(data).toEqual(mockData);
            done();
        });
    }
});

我无法重写您的测试,所以它实际测试它,因为它取决于EndpointLocatorService 的工作方式,您也可以选择在该级别模拟它。我通常选择在HttpClient 级别进行模拟,因为我可以从网络选项卡复制粘贴响应。

关键是您需要模拟服务使用的服务,而不是服务本身。您可以选择模拟组件使用的直接服务,也可以在更深层次上模拟。例如,您的EndpointLocatorService 可能需要调用HttpClient,您可以选择模拟任一服务。

https://angular.io/guide/testing#httpclienttestingmodulehttps://alligator.io/angular/testing-httpclient/

【讨论】:

  • 嗯,所以你在第一种情况下所说的是我应该像这样期望:expect(endLocator.locate).toHaveBeenCalled();之后是我的 cancelService 代码?
  • 你的问题是为什么你没有得到报道,我解释说这是因为你嘲笑了服务本身。不幸的是,将您的代码转换为正确测试并不是一两行就能完成的事情。您应该了解如何按照我的建议测试调用HttpClient 的服务。请参阅答案中的链接。这个想法是,如果你模拟所有的 XHR 调用,你不需要模拟任何其他东西(甚至你的定位器服务),你可以调用你的组件的 API 并检查它的结果。您可以选择混合,但为了保持一致性,我们在 HttpClient 上这样做
  • 对于第一个,您通常不需要确保调用了 locate,因为您正在模拟它,因此其余代码应该可以正常工作并且您的断言将使肯定的。但是确保它被调用并没有什么不好。我总是更喜欢测试 API 本身,而不是确保调用了某个方法。
  • 读一读,试试看。如果你不能让它工作。在了解 HttpClient 测试后发布一个新问题,并将其链接到这个问题,以便我收到通知
  • 谢谢,我试试,呵呵,这对我来说是新的,有点困惑。
猜你喜欢
  • 2020-06-24
  • 1970-01-01
  • 1970-01-01
  • 2019-04-19
  • 2019-09-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多