มีวิธีทดสอบ EventEmitter ใน Angular2 ไหม


88

ฉันมีส่วนประกอบที่ใช้ EventEmitter และ EventEmitter ถูกใช้เมื่อมีคนคลิกในเพจ มีวิธีใดบ้างที่ฉันสามารถสังเกต EventEmitter ระหว่างการทดสอบหน่วยและใช้ TestComponentBuilder เพื่อคลิกองค์ประกอบที่ทริกเกอร์เมธอด EventEmitter.next () และดูสิ่งที่ส่ง


คุณสามารถจัดเตรียมไม้กระดานที่แสดงสิ่งที่คุณได้ลองแล้วฉันสามารถดูเพื่อเพิ่มชิ้นส่วนที่ขาดหายไป
GünterZöchbauer

คำตอบ:


212

การทดสอบของคุณอาจเป็น:

it('should emit on click', () => {
   const fixture = TestBed.createComponent(MyComponent);
   // spy on event emitter
   const component = fixture.componentInstance; 
   spyOn(component.myEventEmitter, 'emit');

   // trigger the click
   const nativeElement = fixture.nativeElement;
   const button = nativeElement.querySelector('button');
   button.dispatchEvent(new Event('click'));

   fixture.detectChanges();

   expect(component.myEventEmitter.emit).toHaveBeenCalledWith('hello');
});

เมื่อส่วนประกอบของคุณคือ:

@Component({ ... })
class MyComponent {
  @Output myEventEmitter = new EventEmitter<string>();

  buttonClick() {
    this.myEventEmitter.emit('hello');
  }
}

1
หากเป็นจุดยึดที่ฉันคลิกแทนที่จะเป็นปุ่มตัวเลือกการสืบค้นจะเป็นเพียงปุ่มแทนหรือไม่ ฉันใช้อะไรบางอย่างที่เหมือนกับส่วนประกอบนั้น แต่ 'expect (value) .toBe (' hello ');' ไม่เคยวิ่ง ฉันสงสัยว่าเป็นเพราะมันเป็นจุดยึดแทน
tallkid24

ฉันอัปเดตคำตอบด้วยวิธีทดสอบที่สะอาดกว่าโดยใช้สายลับแทนที่จะเป็นตัวปล่อยจริงและฉันคิดว่ามันน่าจะใช้ได้ (นั่นคือสิ่งที่ฉันทำกับตัวอย่างใน ebook ของฉันจริงๆ)
cexbrayat

ขอบคุณมาก! ฉันยังใหม่กับการพัฒนาส่วนหน้าโดยเฉพาะการทดสอบหน่วย สิ่งนี้ช่วยได้มาก ฉันไม่รู้ด้วยซ้ำว่ามีฟังก์ชัน spyOn อยู่
tallkid24

ฉันจะทดสอบสิ่งนี้ได้อย่างไรหากใช้ TestComponent เพื่อรวม MyComponent ตัวอย่างเช่น html = <my-component (myEventEmitter)="function($event)"></my-component>และในการทดสอบฉันทำ: tcb.overrideTemplate (TestComponent, html) .createAsync (TestComponent)
bekos

1
คำตอบที่ยอดเยี่ยม - กระชับและตรงประเด็น - รูปแบบทั่วไปที่มีประโยชน์มาก
danday74

48

คุณสามารถใช้สายลับได้ขึ้นอยู่กับสไตล์ของคุณ นี่คือวิธีที่คุณจะใช้สายลับได้อย่างง่ายดายเพื่อดูว่าemitถูกไล่ออกหรือไม่ ...

it('should emit on click', () => {
    spyOn(component.eventEmitter, 'emit');
    component.buttonClick();
    expect(component.eventEmitter.emit).toHaveBeenCalled();
    expect(component.eventEmitter.emit).toHaveBeenCalledWith('bar');
});

ฉันได้อัปเดตคำตอบว่าอย่าใช้ async หรือ fakeAsync โดยไม่จำเป็นซึ่งอาจเป็นปัญหาตามที่ระบุไว้ในความคิดเห็นก่อนหน้านี้ คำตอบนี้ยังคงเป็นทางออกที่ดีสำหรับ Angular 9.1.7 หากมีอะไรเปลี่ยนแปลงโปรดแสดงความคิดเห็นและฉันจะอัปเดตคำตอบนี้ ขอบคุณสำหรับทุกคนที่แสดงความคิดเห็น / กลั่นกรอง
Joshua Michael Wagoner

คุณควรexpectเป็นสายลับที่แท้จริง (ผลจากการspyOn()โทร) ไม่ใช่หรือ?
Yuri

ฉันพลาด "component.buttonClick ()" หลังจาก Spyon วิธีนี้ช่วยแก้ปัญหาของฉันได้ ขอบคุณมาก!
Pearl

2

คุณสามารถสมัครใช้งานตัวปล่อยหรือเชื่อมโยงได้หากเป็น@Output()แม่แบบในแม่แบบหลักและตรวจสอบในองค์ประกอบหลักว่ามีการอัปเดตการเชื่อมโยงหรือไม่ คุณยังสามารถจัดส่งเหตุการณ์การคลิกจากนั้นการสมัครใช้งานจะเริ่มทำงาน


ดังนั้นถ้าฉันชอบ emitter.subscribe (data => {}); ฉันจะรับเอาต์พุต () ถัดไปได้อย่างไร
tallkid24

ตรง หรือเทมเพลตในTestComponenthas <my-component (someEmitter)="value=$event">(โดยที่someEmitterเป็น@Output()) ดังนั้นvalueคุณสมบัติของTextComponentควรจะถูกอัพเดตด้วยเหตุการณ์ที่ส่ง
GünterZöchbauer

0

ฉันมีความต้องการที่จะทดสอบความยาวของอาร์เรย์ที่ปล่อยออกมา นี่คือวิธีที่ฉันทำกับคำตอบอื่น ๆ

expect(component.myEmitter.emit).toHaveBeenCalledWith([anything(), anything()]);

0

แม้ว่าคำตอบที่ได้รับการโหวตสูงสุดจะใช้ได้ผล แต่ก็ไม่ได้แสดงให้เห็นถึงแนวทางปฏิบัติในการทดสอบที่ดีดังนั้นฉันจึงคิดว่าจะขยายคำตอบของGünterด้วยตัวอย่างที่ใช้ได้จริง

ลองจินตนาการว่าเรามีส่วนประกอบง่ายๆดังต่อไปนี้:

@Component({
  selector: 'my-demo',
  template: `
    <button (click)="buttonClicked()">Click Me!</button>
  `
})
export class DemoComponent {
  @Output() clicked = new EventEmitter<string>();

  constructor() { }

  buttonClicked(): void {
    this.clicked.emit('clicked!');
  }
}

ส่วนประกอบคือระบบที่อยู่ระหว่างการทดสอบการสอดแนมบางส่วนของมันทำให้การห่อหุ้มแตก การทดสอบส่วนประกอบเชิงมุมควรรู้สามสิ่งเท่านั้น:

  • DOM (เข้าถึงผ่านเช่นfixture.nativeElement.querySelector);
  • ชื่อของ@Inputs และ@Output s; และ
  • บริการร่วมกัน (ฉีดผ่านระบบ DI)

สิ่งใดก็ตามที่เกี่ยวข้องกับการเรียกใช้วิธีการโดยตรงบนอินสแตนซ์หรือการสอดแนมในส่วนต่างๆของส่วนประกอบนั้นเชื่อมโยงกับการนำไปใช้งานมากเกินไปและจะเพิ่มแรงเสียดทานให้กับการปรับโครงสร้างใหม่ - ควรใช้การทดสอบสองเท่าสำหรับผู้ทำงานร่วมกันเท่านั้น ในกรณีนี้เนื่องจากเราไม่มีผู้ทำงานร่วมกันเราจึงไม่จำเป็นต้องมีการล้อเลียนสายลับหรือคู่ทดสอบอื่น ๆ


วิธีหนึ่งในการทดสอบสิ่งนี้คือสมัครสมาชิกโดยตรงกับตัวปล่อยจากนั้นเรียกใช้การดำเนินการคลิก (ดูส่วนประกอบที่มีอินพุตและเอาต์พุต ):

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

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

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

  it('should emit when clicked', () => {
    let emitted: string;
    component.clicked.subscribe((event: string) => {
      emitted = event;
    });

    fixture.nativeElement.querySelector('button').click();

    expect(emitted).toBe('clicked!');
  });
});

แม้ว่าสิ่งนี้จะโต้ตอบโดยตรงกับอินสแตนซ์คอมโพเนนต์ แต่ชื่อของ@Outputมันก็เป็นส่วนหนึ่งของ API สาธารณะดังนั้นจึงไม่รวมกันแน่นเกินไป


หรือคุณสามารถสร้างโฮสต์ทดสอบอย่างง่าย (ดูส่วนประกอบภายในโฮสต์ทดสอบ ) และติดตั้งส่วนประกอบของคุณ:

@Component({
  selector: 'test-host',
  template: `
    <my-demo (clicked)="onClicked($event)"></my-demo>
  `
})
class TestHostComponent {
  lastClick = '';

  onClicked(value: string): void {
    this.lastClick = value;
  }
}

จากนั้นทดสอบส่วนประกอบในบริบท:

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

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

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

  it('should emit when clicked', () => {
    fixture.nativeElement.querySelector('button').click();

    expect(component.lastClick).toBe('clicked!');
  });
});

componentInstanceนี่เป็นเจ้าภาพการทดสอบเพื่อให้เราสามารถมีความมั่นใจเราไม่ได้คู่สุดเหวี่ยงไปยังส่วนที่เรากำลังทดสอบจริง

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.