Unit testing click event in Angular

前端 未结 5 1888
闹比i
闹比i 2020-12-04 09:00

I\'m trying to add unit tests to my Angular 2 app. In one of my components, there is a button with a (click) handler. When the user clicks the button a function

相关标签:
5条回答
  • 2020-12-04 09:46

    Events can be tested using the async/fakeAsync functions provided by '@angular/core/testing', since any event in the browser is asynchronous and pushed to the event loop/queue.

    Below is a very basic example to test the click event using fakeAsync.

    The fakeAsync function enables a linear coding style by running the test body in a special fakeAsync test zone.

    Here I am testing a method that is invoked by the click event.

    it('should', fakeAsync( () => {
        fixture.detectChanges();
        spyOn(componentInstance, 'method name'); //method attached to the click.
        let btn = fixture.debugElement.query(By.css('button'));
        btn.triggerEventHandler('click', null);
        tick(); // simulates the passage of time until all pending asynchronous activities finish
        fixture.detectChanges();
        expect(componentInstance.methodName).toHaveBeenCalled();
    }));
    

    Below is what Angular docs have to say:

    The principle advantage of fakeAsync over async is that the test appears to be synchronous. There is no then(...) to disrupt the visible flow of control. The promise-returning fixture.whenStable is gone, replaced by tick()

    There are limitations. For example, you cannot make an XHR call from within a fakeAsync

    0 讨论(0)
  • 2020-12-04 09:51

    My objective is to check if the 'onEditButtonClick' is getting invoked when the user clicks the edit button and not checking just the console.log being printed.

    You will need to first set up the test using the Angular TestBed. This way you can actually grab the button and click it. What you will do is configure a module, just like you would an @NgModule, just for the testing environment

    import { TestBed, async, ComponentFixture } from '@angular/core/testing';
    
    describe('', () => {
      let fixture: ComponentFixture<TestComponent>;
      let component: TestComponent;
    
      beforeEach(async(() => {
        TestBed.configureTestingModule({
          imports: [ ],
          declarations: [ TestComponent ],
          providers: [  ]
        }).compileComponents().then(() => {
          fixture = TestBed.createComponent(TestComponent);
          component = fixture.componentInstance;
        });
      }));
    });
    

    Then you need to spy on the onEditButtonClick method, click the button, and check that the method was called

    it('should', async(() => {
      spyOn(component, 'onEditButtonClick');
    
      let button = fixture.debugElement.nativeElement.querySelector('button');
      button.click();
    
      fixture.whenStable().then(() => {
        expect(component.onEditButtonClick).toHaveBeenCalled();
      });
    }));
    

    Here we need to run an async test as the button click contains asynchronous event handling, and need to wait for the event to process by calling fixture.whenStable()

    See Also:

    UPDATE

    It is now preferred to use fakeAsync/tick combo as opposed to the async/whenStable combo. The latter should be used if there is an XHR call made, as fakeAsync does not support it. So instead of the above code, refactored, it would look like

    it('should', fakeAsync(() => {
      spyOn(component, 'onEditButtonClick');
    
      let button = fixture.debugElement.nativeElement.querySelector('button');
      button.click();
      tick();
      expect(component.onEditButtonClick).toHaveBeenCalled();
    
    }));
    

    Don't forget to import fakeAsync and tick

    • Testing documentation
    0 讨论(0)
  • 2020-12-04 09:54

    I'm using Angular 6. I followed Mav55's answer and it worked. However I wanted to make sure if fixture.detectChanges(); was really necessary so I removed it and it still worked. Then I removed tick(); to see if it worked and it did. Finally I removed the test from the fakeAsync() wrap, and surprise, it worked.

    So I ended up with this:

    it('should call onClick method', () => {
      const onClickMock = spyOn(component, 'onClick');
      fixture.debugElement.query(By.css('button')).triggerEventHandler('click', null);
      expect(onClickMock).toHaveBeenCalled();
    });
    

    And it worked just fine.

    0 讨论(0)
  • 2020-12-04 09:55

    I had a similar problem (detailed explanation below), and I solved it (in jasmine-core: 2.52) by using the tick function with the same (or greater) amount of milliseconds as in original setTimeout call.

    For example, if I had a setTimeout(() => {...}, 2500); (so it will trigger after 2500 ms), I would call tick(2500), and that would solve the problem.

    What I had in my component, as a reaction on a Delete button click:

    delete() {
        this.myService.delete(this.id)
          .subscribe(
            response => {
              this.message = 'Successfully deleted! Redirecting...';
              setTimeout(() => {
                this.router.navigate(['/home']);
              }, 2500); // I wait for 2.5 seconds before redirect
            });
      }
    

    Her is my working test:

    it('should delete the entity', fakeAsync(() => {
        component.id = 1; // preparations..
        component.getEntity(); // this one loads up the entity to my component
        tick(); // make sure that everything that is async is resolved/completed
        expect(myService.getMyThing).toHaveBeenCalledWith(1);
        // more expects here..
        fixture.detectChanges();
        tick();
        fixture.detectChanges();
        const deleteButton = fixture.debugElement.query(By.css('.btn-danger')).nativeElement;
        deleteButton.click(); // I've clicked the button, and now the delete function is called...
    
        tick(2501); // timeout for redirect is 2500 ms :)  <-- solution
    
        expect(myService.delete).toHaveBeenCalledWith(1);
        // more expects here..
      }));
    

    P.S. Great explanation on fakeAsync and general asyncs in testing can be found here: a video on Testing strategies with Angular 2 - Julie Ralph, starting from 8:10, lasting 4 minutes :)

    0 讨论(0)
  • 2020-12-04 09:55

    to check button call event first we need to spy on method which will be called after button click so our first line will be spyOn spy methode take two arguments 1) component name 2) method to be spy i.e: 'onSubmit' remember not use '()' only name required then we need to make object of button to be clicked now we have to trigger the event handler on which we will add click event then we expect our code to call the submit method once

    it('should call onSubmit method',() => {
        spyOn(component, 'onSubmit');
        let submitButton: DebugElement = 
        fixture.debugElement.query(By.css('button[type=submit]'));
        fixture.detectChanges();
        submitButton.triggerEventHandler('click',null);
        fixture.detectChanges();
        expect(component.onSubmit).toHaveBeenCalledTimes(1);
    });
    
    0 讨论(0)
提交回复
热议问题