单元测试单击角度事件

我正在尝试将单元测试添加到我的 Angular 2应用程序中。在我的一个组件中,有一个带有 (click)处理程序的按钮。当用户单击按钮时,将调用在 .ts类文件中定义的函数。该函数在 console.log 窗口中打印一条消息,说明按钮已被按下。我目前的测试代码测试打印 console.log消息:

describe('Component: ComponentToBeTested', () => {
var component: ComponentToBeTested;


beforeEach(() => {
component = new ComponentToBeTested();
spyOn(console, 'log');
});


it('should call onEditButtonClick() and print console.log', () => {
component.onEditButtonClick();
expect(console.log).toHaveBeenCalledWith('Edit button has been clicked!);
});
});

但是,这只测试控制器类,而不测试 HTML。我不仅想测试在调用 onEditButtonClick时发生日志记录; 我还想测试当用户单击组件的 HTML 文件中定义的编辑按钮时调用 onEditButtonClick。我该怎么做?

232088 次浏览

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()

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.

See also:

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

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 :)

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.

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);
});