Steve Fitzsimons
Steve Fitzsimons

Reputation: 3904

Angular 5 component testing select and triggered event

I have a component which has a dropdown. When changed it triggers an event which filters through an array to get the selectedProduct from an array based on the event value.

My code is as follows:

  public onProductChanged(event): void {
    this.selectedProduct = this.products.find((product: Products) => product.id == event.target.value);
  }

My select dropdown:

<select id="product" (change)="onProductChanged($event)">
    <option>--- Please Select ---</option>
    <option *ngFor="let product of products" [value]="product.id"> {{ product.displayName }} </option>
</select>

The product object is an object:

{ "id": 1, "name": "name_here", "displayName": "Name Here" }

This all works however I want to test in my component test that changing the select value triggers the event and the correct value is retrieved.

My test code is as follows:

  describe('Product selection', () => {
    it('should select product', () => {

      expect(component.selectedProduct).toBeUndefined();
      productSelectElement.nativeElement.value = 'Product Name';
      productSelectElement.nativeElement.dispatchEvent(new Event('change'));

      fixture.detectChanges();

      expect(component.onProductChanged).toHaveBeenCalled();
      expect(component.selectedProduct).toEqual({ "id": 1, "name": "product_name", "displayName": "Product Name" });
    });
  });

The productChanged event has been called and that test passes. My selectedProduct is however always null. How do I get the event to fire using the changed value in the dropdown?

Upvotes: 7

Views: 12437

Answers (2)

nesdis
nesdis

Reputation: 1220

This is a great question and angular has evolved much since it was asked. There is very little documentation (even on Angular.dev) on how to implement or test <select> input.

component.ts should look like this:

public onProductChanged(product): void {
  this.selectedProduct = product
}

component.html:

<select id="product" (ngModelChange)="onProductChanged($event)">
  <option>--- Please Select ---</option>
  <option *ngFor="let product of products" [ngValue]="product">
    {{ product.displayName }} 
  </option>
</select>

component.spec.ts:

describe('Product selection', () => {
  it('should select product', () => {

    expect(component.selectedProduct).toBeUndefined();
    productSelectElement.nativeElement.value = 
      productSelectElement.nativeElement.options[1].value;
    productSelectElement.triggerEventHandler('ngModelChange', { "id": 1, 
      "name": "product_name", "displayName": "Product Name" });

    fixture.detectChanges();

    expect(component.onProductChanged).toHaveBeenCalled();
    expect(component.selectedProduct).toEqual({ "id": 1,
     "name": "product_name", "displayName": "Product Name" });
   });
});

Upvotes: 0

Steve Fitzsimons
Steve Fitzsimons

Reputation: 3904

Turns out that in the before each I had set a spyOn for the function without a call through. Working code is as follows:

  beforeEach(() => {
    fixture = TestBed.createComponent(SelectProductsComponent);
    component = fixture.componentInstance;
    component.products = products;
    fixture.detectChanges();

    productSelectElement = fixture.debugElement.query(By.css('#products'));

    spyOn(component, 'onProductChanged').and.callThrough();

    expect(component.products).toEqual(products);
    expect(component.selectedProduct).toBeUndefined();
  });

  describe('Product selection', () => {
    it('should select product', () => {

      productSelectElement.nativeElement.value = 1;
      productSelectElement.nativeElement.dispatchEvent(new Event('change'));

      fixture.detectChanges();

      expect(component.onProductChanged).toHaveBeenCalled();
      expect(component.selectedProduct).toEqual({ "id": 1, "name": "product_name", "displayName": "Product Name" });

    });
  });

Upvotes: 12

Related Questions