angularangular-materialjasminekarma-jasmineangular-forms

Angular Test harness not setting ngModel variable in MatSelect


Using MatSelect and working on UTs. Post selecting a option via test harness, not able to see ngModel variable binding working.

Here is the html snippet:

    <mat-form-field class="dropdown-container">
      <mat-label>Select issue type</mat-label>
      <mat-select [(ngModel)]="selectedIssueType" (ngModelChange)="filterIssueType()" id="issuetype-filter">
        <mat-option
          *ngFor="let issueLinkUnique of issueTypeList" 
          value={{issueLinkUnique}}>
            {{issueLinkUnique}}
        </mat-option>
      </mat-select>
    </mat-form-field>

Here is the unit test case:

    it('dummy test', async () => {
        serviceSpy.and.returnValue(of(rtConsolidatedLinkedIssesTableData)); // mocking data 
        component.ngOnInit(); // initialization data
        const issueTypeFilter = await loader.getHarness<MatSelectHarness>(MatSelectHarness.with({
            selector: '#issuetype-filter'}));
        await issueTypeFilter.open();
        expect((await issueTypeFilter.getOptions()).length).toBe(2); // correct
        const blockedOption = await issueTypeFilter.getOptions({text: 'Blocked'});
        await blockedOption[0].click();
        expect(await blockedOption[0].isSelected()).toBe(true); // correct
        fixture.whenStable();
        expect(component.selectedIssueType).toBe('Block'); // it's having default value

    });

Here is the stackblitz link


Solution

  • The FormsModule was not added to the component in the stackblitz.

    import { FormsModule } from '@angular/forms';
    
    /** @title Select with 2-way value binding */
    @Component({
      selector: 'select-value-binding-example',
      templateUrl: 'select-value-binding-example.html',
      standalone: true,
      imports: [MatFormFieldModule, MatSelectModule, FormsModule],
    })
    export class SelectValueBindingExample {
      selected = 'option2';
    }
    

    Please ensure the click the correct option that matches the test case:

    it('dummy test', async () => {
      const issueTypeFilter = await loader.getHarness<MatSelectHarness>(
        MatSelectHarness.with({
          selector: '#issuetype-filter',
        })
      );
      await issueTypeFilter.open();
      expect((await issueTypeFilter.getOptions()).length).toBe(4); // correct
      const options = await issueTypeFilter.getOptions();
    
      await options[1].click();
      await fixture.detectChanges();
      expect(await issueTypeFilter.getValueText()).toBe('Option 1');
      expect(component.selected).toBe('option1'); // binding value should change
    });
    

    Full Code:

    Test:

    import { HarnessLoader } from '@angular/cdk/testing';
    import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
    import { MatSelectHarness } from '@angular/material/select/testing';
    import { MatTableHarness } from '@angular/material/table/testing';
    import { SelectValueBindingExample } from './select-value-binding-example';
    import { ComponentFixture, TestBed } from '@angular/core/testing';
    import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
    import { MatSelectModule } from '@angular/material/select';
    
    let loader: HarnessLoader;
    describe('test', () => {
      let component: SelectValueBindingExample;
      let fixture: ComponentFixture<SelectValueBindingExample>;
    
      beforeEach(async () => {
        await TestBed.configureTestingModule({
          imports: [BrowserAnimationsModule, MatSelectModule],
        }).compileComponents();
        fixture = TestBed.createComponent(SelectValueBindingExample);
        loader = TestbedHarnessEnvironment.loader(fixture);
        component = fixture.componentInstance;
        fixture.detectChanges();
      });
    
      it('dummy test', async () => {
        const issueTypeFilter = await loader.getHarness<MatSelectHarness>(
          MatSelectHarness.with({
            selector: '#issuetype-filter',
          })
        );
        await issueTypeFilter.open();
        expect((await issueTypeFilter.getOptions()).length).toBe(4); // correct
        const options = await issueTypeFilter.getOptions();
    
        await options[1].click();
        await fixture.detectChanges();
        expect(await issueTypeFilter.getValueText()).toBe('Option 1');
        expect(component.selected).toBe('option1'); // binding value should change
      });
    });
    

    HTML:

    <mat-form-field>
      <mat-label>Select an option</mat-label>
      <mat-select [(ngModel)]="selected" id="issuetype-filter">
        <mat-option>None</mat-option>
        <mat-option value="option1">Option 1</mat-option>
        <mat-option value="option2">Option 2</mat-option>
        <mat-option value="option3">Option 3</mat-option>
      </mat-select>
    </mat-form-field>
    
    <p>You selected: {{selected}}</p>
    

    TS:

    import { Component } from '@angular/core';
    import { MatSelectModule } from '@angular/material/select';
    import { MatFormFieldModule } from '@angular/material/form-field';
    import { FormsModule } from '@angular/forms';
    
    /** @title Select with 2-way value binding */
    @Component({
      selector: 'select-value-binding-example',
      templateUrl: 'select-value-binding-example.html',
      standalone: true,
      imports: [MatFormFieldModule, MatSelectModule, FormsModule],
    })
    export class SelectValueBindingExample {
      selected = 'option2';
    }
    

    Stackblitz Demo