簡體   English   中英

Jasmine 嘗試監視在 ngOnInit 內部調用的服務方法時未達到間諜 callFake

[英]Jasmine spy callFake not being reached when trying spy on a service method that is called inside ngOnInit

我正在嘗試為使用服務執行 HTTP 請求以從數據庫檢索信息的組件設置一些單元測試。 我對 angular 和單元測試還很陌生,所以請耐心等待。 在我的測試中,我試圖監視名為getClients的 function(這個 function 本質上是實際執行 HTTP 請求的服務的處理程序)並使用callFake

我遇到的問題是getClients function 沒有被覆蓋,這讓我相信間諜不起作用或者沒有監視我認為的東西。 我可以看出它沒有被調用,因為失敗消息引用了真實getClients function 中的內容。

測試代碼

我在這里的理解是,因為我試圖監視的 function 在ngOnInit function 中,所以我必須先定義間諜,然后實例化組件。 我也試過在其中運行間諜程序, it也沒有用。

describe('handleID', () => {

    beforeEach(waitForAsync (() => {

        spyOn(service, 'getClients').and.callFake(() => {
            let companyList = [
                {
                    COMPANYDESCRIPTOR: "Hello World Inc.",
                    COMPANYNAME: "Hello World Inc.",
                    CUSTOMERID: "abcdef123456",
                    CUSTOMERKEY: 123456
                }
            ]
            
            component.companySearchService.companies.next(companyList);
            return companyList;
        });

        fixture = TestBed.createComponent(CompanySearchComponent);
        component = fixture.componentInstance;
        service = component.companySearchService;
        fixture.detectChanges();
    }));

    it("should update component.companyForm.controls['selectedCompany'] to 'Hello World Inc.'", () => {
        component.companyForm = component._formBuilder.group({
            selectedCompany: ['']
        })
        

        component.pathToNameProp = 'COMPANYNAME';
        component.pathToIdProp = ['CUSTOMERID', 'CUSTOMERKEY'];

        let id = 123456;

        component.handleID(id);

        expect(component.companyForm.get('selectedCompany')).toBe('Hello World Inc.');
    })

})

實際 function

為了清楚起見,我在下面提供了getClients function。 dbService是進行 API 調用的數據庫服務。 makeAnApiCall返回一個observable對象, subscribe只是將數據傳遞給另一個處理程序,該處理程序根據source確定如何處理數據。

getClients(endpoint, method, options = []) {
    this.loading.next(true);
    this.dbService
        .makeAnApiCall(endpoint, method, options)
        .subscribe(
            res => this.generateCompanyList(res, this.source.getValue())
        )
}

失敗信息

失敗消息引用了從數據庫服務的makeAnApiCall方法返回的可見訂閱。 這讓我相信間諜根本沒有被創造出來,或者完全在監視其他東西。

Failed: Cannot read properties of undefined (reading 'subscribe')
    at CompanySearchService.getClients (http://localhost:9876/_karma_webpack_/main.js:6343:13)
    at CompanySearchComponent.ngOnInit (http://localhost:9876/_karma_webpack_/webpack:/src/app/utilities/company-search/company-search.component.ts:98:39)
    ...

問題

  1. 為什么間諜不起作用?
  2. 關於單元測試,在處理不需要完全避免的可觀察對象、承諾和 HTTP 請求時,是否有更好的方法來編寫單元測試?

在此先感謝您提供的所有幫助!

據我所知,您缺少在創建組件之前執行TestBed.configureTestingModule的操作。 這需要聲明被測組件並提供類似於普通 ngModule 的服務。 最好查看Angular 測試指南以了解如何使用它。

在測試模塊中,您將在 providers 數組中設置 CompanySearchService,然后您可以使用TestBed.inject(CompanySearchService)訪問它並模擬您想要的方法。

這個解決方案解決了我的問題,但它仍然沒有真正回答我的問題。 因此,如果有人可以對此提供更多說明,我很樂意將他們的回復標記為已接受的答案。

單元測試

看起來我調用spyOnfixture.detectChangescomponent.ngOnInit的順序擾亂了服務,因此我的Cannot read properties of undefined錯誤。 下面更新的代碼是完整的單元測試。 我創建了__beforeEach ,所以我不必在嵌套單元測試中重復所有內容。 我最終也完全取消了調用component.ngOnInit() ,因為它似乎detectChanges也能正常工作。

describe('CompanySearchComponent', () => {

    let fixture, component, service;

    let __beforeEach = () => {
        fixture = TestBed.createComponent(CompanySearchComponent);
        component = fixture.componentInstance;
        service = component.companySearchService;
        
        component.companySource = 'database';
        spyOn(service, 'getClients').and.callFake(() => {
            let response = { data: { rows:[
                        {
                            COMPANYDESCRIPTOR: "Hello World Inc.",
                            COMPANYNAME: "Hello World Inc.",
                            CUSTOMERID: "abcdef123456",
                            CUSTOMERKEY: 123456
                        }
                    ]}}
             component.companySearchService.generateCompanyList(response, 'database');
        });

        fixture.detectChanges();
    }

    beforeAll(waitForAsync(__beforeEach));

    it ('should initialize the component and the service', () => {
        expect(component).toBeDefined();
        expect(service).toBeDefined();
    })

    it ('should initalize pathToNameProp to \'COMPANYNAME\' and pathToProp to [\'CUSTOMERID\', \'CUSTOMERKEY\'] with source set to \'database\'', () => {
        expect(component.pathToNameProp).toBe('COMPANYNAME');
        expect(component.pathToIdProp).toEqual(['CUSTOMERID', 'CUSTOMERKEY']);
    })

    it ('should update companies array', () => {
        expect(component.companies).toEqual([
            {
                COMPANYDESCRIPTOR: "Hello World Inc.",
                COMPANYNAME: "Hello World Inc.",
                CUSTOMERID: "abcdef123456",
                CUSTOMERKEY: 123456
            }
        ]);
    })

    describe('handleID', () => {
        
        beforeAll(waitForAsync(__beforeEach));

        it("should update selectedCompany form'", () => {
            let id = '123456';
            component.handleID(id);
            expect(component.companyForm.controls['selectedCompany'].value.COMPANYNAME).toBe('Hello World Inc.');
        })
    })
})

設置源

這可能不相關,但我想提出我遇到的另一個問題。 該組件不是獨立的,其他組件將其作為依賴項導入。 也就是說,必須顯式定義companySearchComponent.companySource然后重新初始化組件,因為它的值是在構造函數中定義的。

constructor(
    private elem: ElementRef,
    public companySearchService: CompanySearchService,
    private hierarchyService: HierarchyService, 
    private _formBuilder: FormBuilder
) {
    this.companySource = this.elem.nativeElement.dataset.companySrc;
    this.prevCompanySrc = this.companySearchService.source.getValue();
}

構造函數引用源的選擇器元素

<company-search [hidden]="!showSearch" id="company-search" data-company-src="database"></company-search>

companySearchComponent.ngOnInit中,源值用於定義 http 請求和響應的一些重要屬性。 它也用於對 companySearchService.getClients companySearchService.getClients()的初始調用(我最初遇到問題的 function)。

ngOnInit() {

    switch(this.companySource) {
        case 'database':
            ...
            this.pathToNameProp = 'COMPANYNAME';
            this.pathToIdProp = ['CUSTOMERID', 'CUSTOMERKEY'];
            break;
        ...
        default:
            break;
    }

    if (!this.companySearchService.companies.value || this.prevCompanySrc !== this.companySource) {
        this.companySearchService.source.next(this.companySource);
        this.companySearchService.getClients(this.endpoint, this.httpMethod, this.httpOptions);
    }

    ...
}

就像我說的,這並不能完全回答我提出的問題,但它是問題的解決方案,所以如果有人發布更全面、更徹底的解決方案,我會很樂意將其標記為已接受的答案。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM