How to cover test for button click in angular? (St

2020-07-24 06:00发布

I am making a very simple application which has one input box and a button.

  1. Input is for entering email

  2. Subscribe button with event handler

Entering email and click over the button will make an api call, (This method works)

  subscribeEmail() {
    this.error = '';


        if (this.userForm.controls.email.status === 'VALID') {

      const params = new HttpParams()
                .set('EMAIL', this.userForm.controls.email.value)
        .set('subscribe','Subscribe')
        .set('b_aaa7182511d7bd278fb9d510d_01681f1b55','')
      console.log(params);
            const mailChimpUrl = this.mailChimpEndpoint + params.toString();

            this.http.jsonp<MailChimpResponse>(mailChimpUrl, 'c').subscribe(response => {
        console.log('response ', response)
                if (response.result && response.result !== 'error') {
                    this.submitted = true;
                }
                else {
                    this.error = response.msg;
                }
            }, error => {
                console.error(error);
                this.error = 'Sorry, an error occurred.';
            });
        }
  }

A complete working example here

There is no issues with it and you can also check everything works fine.

Requirement: I am in the need to cover test case for this button click and the http call with params.

I am unable to write the test case and it is showing that the tests are not covered for the http calls with params.

enter image description here

The tests that I have written so for for achieving the button click scenario like,

describe('HelloComponent', () => {

  let component: HelloComponent;
  let fixture: ComponentFixture<HelloComponent>;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
       imports: [
        HttpClientTestingModule,
        ReactiveFormsModule
      ],
      declarations: [HelloComponent]
    }).compileComponents();
  }));

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

  it('check subscribeEmail function has value', (done: DoneFn) => {

    const subscribeButton = fixture.debugElement.query(By.css('.subscribe'));
    subscribeButton.triggerEventHandler('click', {});

    done();
  });
});

And the Working stackblitz with test cases here

(Please look into hello.component.spec.ts )

For button click, I have covered and it works

    const subscribeButton = fixture.debugElement.query(By.css('.subscribe'));
    subscribeButton.triggerEventHandler('click', {});

Could you please help me to achieve the result of covering the test case for the button click that has http call with params?

Update: After some search I have found that I can use httpmock to call the url with params but I am ending up with error as,

Error: Expected one matching request for criteria "Match URL: https://gmail.us10.list-manage.com/subscribe/post-json?u=aaa7182511d7bd278fb9d510d&id=01681f1b55&amp", found none.

You can also see the Modified stackblitz with httpMock here...

After all try only I am making this post so please consider this question and provide right solution.

A big thanks in advance.

2条回答
手持菜刀,她持情操
2楼-- · 2020-07-24 06:16

That was a very long question, I understand that you have put a lot of time to create it. So, I have also put some time to refactor the code to make it as per the best practices. This would require to split your code as a service and make the code better for Unit Testing. (Unit testing for component & service)

  1. Create a service such as MyService and move the http code into it. It would help you unit test component and service separately. I created as below:
export class MyService {
  mailChimpEndpoint = 'https://gmail.us10.list-manage.com/subscribe/post-json?u=aaa7182511d7bd278fb9d510d&amp;id=01681f1b55&amp';
  constructor(private _http: HttpClient) {}

  submitForm(email: string){
    const params = new HttpParams()
                .set('EMAIL', email)
        .set('subscribe','Subscribe')
        .set('b_aaa7182511d7bd278fb9d510d_01681f1b55','')
    const mailChimpUrl = this.mailChimpEndpoint + params.toString();

    return this._http.jsonp<MailChimpResponse>(mailChimpUrl, 'c')
  }
}
  1. Create a Unit test for component as you can see here in the stackblitz . Take a good look at refactored code & how I have covered all cases depending on the scenario. As I have already explained in your previous question, you can test MatSnackBar calls

  2. Similarly, you can write Unit test for service by referring to one my of my articles to test a service

查看更多
欢心
3楼-- · 2020-07-24 06:20

Indeed it would be better to re-factory the code and make it more test friendly. But the provided example still could be covered with tests. The idea to verify the passed parameters and if parameters are correct then return a predictable response as an observable.

so, the this.http.jsonp returns the expected result only if passed parameters are correct.

import { ComponentFixture, TestBed, async, fakeAsync, tick } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { DebugElement, Injector }  from '@angular/core';

import { HelloComponent } from './hello.component';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { ReactiveFormsModule } from '@angular/forms';
import { moqInjectorProviders, resolveMock } from 'ng-auto-moq';
import { Mock, It } from 'moq.ts';
import { HttpClient, HttpParams } from '@angular/common/http';
import { cold, getTestScheduler } from 'jasmine-marbles';


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

  beforeEach(async(() => {
    TestBed.configureTestingModule({
       imports: [
        HttpClientTestingModule,
        ReactiveFormsModule
      ],
      declarations: [HelloComponent],
      // setups automatically all dependecies of the component as mock objects
      providers: moqInjectorProviders(HelloComponent)
    }).compileComponents();
  }));

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

  it('check subscribeEmail function has value', fakeAsync(() => {
    const injector = TestBed.get(Injector);

    // sets mock behaviour
    resolveMock(HttpClient, injector)
      // if jsonp is invoked with first parameter with email substring
      .setup(instance => instance.jsonp(It.Is<string>(url => url.indexOf('me@gmail.com') > 1), 'c'))
      // only in this case return a cold observable
      .returns(cold("a", {a: {result: "success"}}));

    const email = fixture.debugElement.query(By.css('.email-input')).nativeElement;
    // sets email value
    email.value = 'me@gmail.com';
    // notifies angualr that element has changed
    email.dispatchEvent(new Event('input'));

    const subscribeButton = fixture.debugElement.query(By.css('.subscribe'));
    subscribeButton.triggerEventHandler('click', {});

    // flush the observable of jsonp
    getTestScheduler().flush();
    fixture.detectChanges();

    expect(component.submitted).toBe(true);
  }));

  it('sets error when server returns an error', fakeAsync(() => {
    const injector = TestBed.get(Injector);
    const msg = "erorr message";

    resolveMock(HttpClient, injector)
      .setup(instance => instance.jsonp(It.Is<string>(url => url.indexOf('me@gmail.com') > 1), 'c'))
      .returns(cold("a", {a: {result: "error", msg}}));

    const email = fixture.debugElement.query(By.css('.email-input')).nativeElement;
    email.value = 'me@gmail.com';
    email.dispatchEvent(new Event('input'));

    const subscribeButton = fixture.debugElement.query(By.css('.subscribe'));
    subscribeButton.triggerEventHandler('click', {});

    getTestScheduler().flush();
    fixture.detectChanges();

    expect(component.error).toEqual(msg);
  }));
});

Here you can find an working example

Please, noticed the I used some extra libs like jasmine-marbles to fake observables or moq.ts to mock HttpClient

查看更多
登录 后发表回答