Helpex - Trao đổi & giúp đỡ Đăng nhập
20

Ví dụ đầu tiên

Tôi đã có bài kiểm tra sau:

import { async, ComponentFixture, TestBed } from '@angular/core/testing';

import { Component } from '@angular/core';

@Component({
    template: '<ul><li *ngFor="let state of values | async">{{state}}</li></ul>'
})
export class TestComponent {
    values: Promise<string[]>;
}

describe('TestComponent', () => {
    let component: TestComponent;
    let fixture: ComponentFixture<TestComponent>;
    let element: HTMLElement;

    beforeEach(async(() => {
        TestBed.configureTestingModule({
            declarations: [TestComponent]
        })
            .compileComponents();
    }));

    beforeEach(() => {
        fixture = TestBed.createComponent(TestComponent);
        component = fixture.componentInstance;
        fixture.detectChanges();
        element = (<HTMLElement>fixture.nativeElement);
    });

    it('this test fails', async() => {
        // execution
        component.values = Promise.resolve(['A', 'B']);
        fixture.detectChanges();
        await fixture.whenStable();

        // evaluation
        expect(Array.from(element.querySelectorAll('li')).map(elem => elem.textContent)).toEqual(['A', 'B']);
    });

    it('this test works', async() => {
        // execution
        component.values = Promise.resolve(['A', 'B']);
        fixture.detectChanges();
        await fixture.whenStable();
        fixture.detectChanges();
        await fixture.whenStable();

        // evaluation
        expect(Array.from(element.querySelectorAll('li')).map(elem => elem.textContent)).toEqual(['A', 'B']);
    });
});

Như bạn có thể thấy, có một thành phần siêu đơn giản, chỉ hiển thị danh sách các mục được cung cấp bởi a Promise. Có hai bài kiểm tra, một bài không đạt và một bài đạt. Sự khác biệt duy nhất giữa các bài kiểm tra đó là bài kiểm tra đã vượt qua các cuộc gọi fixture.detectChanges(); await fixture.whenStable();hai lần.

CẬP NHẬT: Ví dụ thứ hai (cập nhật lại vào ngày 31/03/21 2019)

Ví dụ này cố gắng điều tra các mối quan hệ có thể có với ngZone:

import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { FormsModule } from '@angular/forms';
import { Component, NgZone } from '@angular/core';

@Component({
    template: '{{value}}'
})
export class TestComponent {
    valuePromise: Promise<ReadonlyArray<string>>;
    value: string = '-';

    set valueIndex(id: number) {
        this.valuePromise.then(x => x).then(x => x).then(states => {
            this.value = states[id];
            console.log(`value set ${this.value}. In angular zone? ${NgZone.isInAngularZone()}`);
        });
    }
}

describe('TestComponent', () => {
    let component: TestComponent;
    let fixture: ComponentFixture<TestComponent>;

    beforeEach(async(() => {
        TestBed.configureTestingModule({
            imports: [FormsModule],
            declarations: [TestComponent],
            providers: [
            ]
        })
            .compileComponents();
    }));

    beforeEach(() => {
        fixture = TestBed.createComponent(TestComponent);
        component = fixture.componentInstance;
        fixture.detectChanges();
    });

    function diagnoseState(msg) {
        console.log(`Content: ${(fixture.nativeElement as HTMLElement).textContent}, value: ${component.value}, isStable: ${fixture.isStable()} # ${msg}`);
    }

    it('using ngZone', async() => {
        // setup
        diagnoseState('Before test');
        fixture.ngZone.run(() => {
            component.valuePromise = Promise.resolve(['a', 'b']);

            // execution
            component.valueIndex = 1;
        });
        diagnoseState('After ngZone.run()');
        await fixture.whenStable();
        diagnoseState('After first whenStable()');
        fixture.detectChanges();
        diagnoseState('After first detectChanges()');
    });

    it('not using ngZone', async(async() => {
        // setup
        diagnoseState('Before setup');
        component.valuePromise = Promise.resolve(['a', 'b']);

        // execution
        component.valueIndex = 1;

        await fixture.whenStable();
        diagnoseState('After first whenStable()');
        fixture.detectChanges();
        diagnoseState('After first detectChanges()');

        await fixture.whenStable();
        diagnoseState('After second whenStable()');
        fixture.detectChanges();
        diagnoseState('After second detectChanges()');

        await fixture.whenStable();
        diagnoseState('After third whenStable()');
        fixture.detectChanges();
        diagnoseState('After third detectChanges()');
    }));
});

Thử nghiệm đầu tiên này (sử dụng ngZone một cách rõ ràng) cho kết quả:

Content: -, value: -, isStable: true # Before test
Content: -, value: -, isStable: false # After ngZone.run()
value set b. In angular zone? true
Content: -, value: b, isStable: true # After first whenStable()
Content: b, value: b, isStable: true # After first detectChanges()

Nhật ký kiểm tra thứ hai:

Content: -, value: -, isStable: true # Before setup
Content: -, value: -, isStable: true # After first whenStable()
Content: -, value: -, isStable: true # After first detectChanges()
Content: -, value: -, isStable: true # After second whenStable()
Content: -, value: -, isStable: true # After second detectChanges()
value set b. In angular zone? false
Content: -, value: b, isStable: true # After third whenStable()
Content: b, value: b, isStable: true # After third detectChanges()

Tôi hy vọng rằng bài kiểm tra sẽ chạy trong vùng góc cạnh, nhưng nó không. Vấn đề dường như đến từ thực tế là

Để tránh bất ngờ, các hàm được truyền tới then () sẽ không bao giờ được gọi đồng bộ, ngay cả với một lời hứa đã được giải quyết. ( Nguồn )

Trong ví dụ thứ hai này, tôi đã kích động sự cố bằng cách gọi .then(x => x)nhiều lần, điều này sẽ không có tác dụng gì hơn là đưa lại tiến trình vào vòng lặp sự kiện của trình duyệt và do đó làm chậm kết quả. Theo hiểu biết của tôi cho đến nay, lệnh gọi đến await fixture.whenStable()về cơ bản nên nói "đợi cho đến khi hàng đợi đó trống". Như chúng ta có thể thấy, điều này thực sự hoạt động nếu tôi thực thi mã trong ngZone một cách rõ ràng. Tuy nhiên, đây không phải là mặc định và tôi không thể tìm thấy bất kỳ nơi nào trong sách hướng dẫn rằng tôi viết các bài kiểm tra của mình theo cách đó, vì vậy điều này khiến tôi cảm thấy khó xử.

Điều gì await fixture.whenStable()thực sự làm trong bài kiểm tra thứ hai? Các mã nguồn cho thấy rằng trong trường hợp này fixture.whenStable()sẽ chỉ return Promise.resolve(false);. Vì vậy, tôi thực sự đã cố gắng thay thế await fixture.whenStable()bằng await Promise.resolve()và thực sự nó có tác dụng tương tự: Điều này có tác dụng tạm dừng kiểm tra và bắt đầu với hàng đợi sự kiện và do đó lệnh gọi lại được chuyển đến valuePromise.then(...)thực sự được thực thi, nếu tôi chỉ awaitthường xuyên gọi bất kỳ lời hứa nào. đủ.

Tại sao tôi cần gọi await fixture.whenStable();nhiều lần? Tôi đang sử dụng nó sai? Đây có phải là hành vi dự định không? Có bất kỳ tài liệu "chính thức" nào về cách nó dự định hoạt động / cách giải quyết vấn đề này không?

20 hữu ích 2 bình luận 6.4k xem chia sẻ
18

Tôi tin rằng bạn đang trải nghiệm Delayed change detection.

Phát hiện thay đổi trì hoãn là có chủ ý và hữu ích. Nó cung cấp cho người thử nghiệm cơ hội để kiểm tra và thay đổi trạng thái của thành phần trước khi Angular bắt đầu ràng buộc dữ liệu và gọi các móc vòng đời.

DiscoveryChanges ()


Việc triển khai Automatic Change Detectioncho phép bạn chỉ gọi fixture.detectChanges()một lần trong cả hai lần kiểm tra.

 beforeEach(async(() => {
            TestBed.configureTestingModule({
                declarations: [TestComponent],
                providers:[{ provide: ComponentFixtureAutoDetect, useValue: true }] //<= SET AUTO HERE
            })
                .compileComponents();
        }));

Stackblitz

https://stackblitz.com/edit/directive-testing-fnjjqj?embed=1&file=app/app.component.spec.ts

Nhận xét này trong Automatic Change Detectionví dụ là quan trọng và tại sao các bài kiểm tra của bạn vẫn cần gọi fixture.detectChanges(), ngay cả với AutoDetect.

Thử nghiệm thứ hai và thứ ba cho thấy một hạn chế quan trọng. Môi trường thử nghiệm Angular không biết rằng thử nghiệm đã thay đổi tiêu đề của thành phần. Dịch vụ ComponentFixtureAutoDetect phản hồi các hoạt động không đồng bộ như độ phân giải lời hứa, bộ định thời và sự kiện DOM. Nhưng cập nhật trực tiếp, đồng bộ của thuộc tính thành phần là vô hình. Việc kiểm tra phải gọi fixture.detectChanges () theo cách thủ công để kích hoạt một chu kỳ phát hiện thay đổi khác.

Do cách bạn đang giải quyết Lời hứa khi bạn đang đặt nó, tôi nghi ngờ rằng nó đang được coi là một bản cập nhật đồng bộ và Auto Detection Servicesẽ không phản hồi lại nó.

component.values = Promise.resolve(['A', 'B']);

Tự động phát hiện thay đổi


Kiểm tra các ví dụ khác nhau cung cấp manh mối về lý do tại sao bạn cần phải gọi fixture.detectChanges()hai lần mà không có AutoDetect. Lần đầu tiên kích hoạt ngOnInittrong Delayed change detectionmô hình ... gọi là lần thứ hai cập nhật chế độ xem.

Bạn có thể thấy điều này dựa trên các nhận xét ở bên phải fixture.detectChanges()trong ví dụ mã bên dưới

it('should show quote after getQuote (fakeAsync)', fakeAsync(() => {
  fixture.detectChanges(); // ngOnInit()
  expect(quoteEl.textContent).toBe('...', 'should show placeholder');

  tick(); // flush the observable to get the quote
  fixture.detectChanges(); // update view

  expect(quoteEl.textContent).toBe(testQuote, 'should show quote');
  expect(errorMessage()).toBeNull('should not show error');
}));

Các thử nghiệm không đồng bộ khác Ví dụ


Tóm lại: Khi không tận dụng Automatic change detection, việc gọi fixture.detectChanges()sẽ "bước" qua Delayed Change Detectionmô hình ... cho phép bạn có cơ hội kiểm tra và thay đổi trạng thái của thành phần trước khi Angular bắt đầu ràng buộc dữ liệu và gọi các móc vòng đời.

Cũng xin lưu ý nhận xét sau từ các liên kết được cung cấp:

Thay vì tự hỏi khi nào vật cố định kiểm tra sẽ hoặc không thực hiện phát hiện thay đổi, các mẫu trong hướng dẫn này luôn gọi hàm detector () một cách rõ ràng. Không có hại gì khi gọi detector () thường xuyên hơn mức cần thiết.


Ví dụ thứ hai về Stackblitz

Ví dụ thứ hai stackblitz cho thấy rằng nhận xét dòng 53 detectChanges()dẫn đến cùng một console.logđầu ra. Gọi detectChanges()hai lần trước whenStable()là không cần thiết. Bạn đang gọi detectChanges()ba lần nhưng cuộc gọi thứ hai trước đó whenStable()không có bất kỳ tác động nào. Bạn chỉ thực sự đạt được bất cứ điều gì từ hai trong số detectChanges()trong ví dụ mới của bạn.

Không có hại gì khi gọi detector () thường xuyên hơn mức cần thiết.

https://stackblitz.com/edit/directive-testing-cwyzrq?embed=1&file=app/app.component.spec.ts


CẬP NHẬT: Ví dụ thứ hai (cập nhật lại vào ngày 31/03/21 2019)

Cung cấp stackblitz để chứng minh đầu ra khác với các biến thể sau để bạn xem xét.

  • chờ fixture.whenStable ();
  • fixture.whenStable (). then (() => {})
  • chờ fixture.whenStable (). then (() => {})

Stackblitz

https://stackblitz.com/edit/directive-testing-b3p5kg?embed=1&file=app/app.component.spec.ts

18 hữu ích 5 bình luận chia sẻ
0

Theo tôi, bài kiểm tra thứ hai có vẻ sai, nó nên được viết theo mẫu sau:

component.values = Promise.resolve(['A', 'B']);
fixture.whenStable().then(() => {
  fixture.detectChanges();       
  expect(Array.from(element.querySelectorAll('li')).map(elem => elem.textContent)).toEqual(['A', 'B']);
});

Vui lòng xem: Khi sử dụng ổn định

Bạn nên gọi detectChangestrong whenStable()như

Fixture.whenStable () trả về một lời hứa sẽ giải quyết khi hàng đợi tác vụ của công cụ JavaScript trở nên trống.

0 hữu ích 4 bình luận chia sẻ
loading
Không tìm thấy câu trả lời bạn tìm kiếm? Duyệt qua các câu hỏi được gắn thẻ angular angular-test testbed , hoặc hỏi câu hỏi của bạn.

Có thể bạn quan tâm

loading