angularunit-testingangular-unit-test

Angular unit testing - using debugElement.queryAll to get instances of a button


Running this unit test in Angular 21 always fails with the following error:

× should toggle showSettings when settings button is clicked (440231 ms)    
● DrawerComponent › should launch settings modal when button is clicked
expect(jest.fn()).toHaveBeenCalled()
Expected number of calls: >= 1
Received number of calls:    0

it('should launch the Settings modal when button is clicked', () => {
    
    const settingSpy = jest.spyOn(component as any, 'showSettings');
    const buttons = fixture.debugElement.queryAll(By.directive(ButtonComponent));
        
    buttons[4].triggerEventHandler('click', null);
    fixture.detectChanges();
    
    expect(settingSpy).toHaveBeenCalled();
  });

Note: The buttons array (of type DebugElement[]) returns 7 instances of ButtonComponent.

The "Settings" button I'm interested in is actually in index position 4, yet my click event still doesn't work. i.e. The expected num of calls should be "1".

Here's a snippet of the component's template (fyi: is part of our custom UI library) :

<div class="menu-settings">
      <div class="divider"></div>
      <div class="app-controls">
        <my-button
          icon="settings"          
          (buttonClick)="showSettings()" 
        >
      </my-button>
        <my-button          
          icon="info"          
          (buttonClick)="showAboutModal()"          
        ></my-button>
      </div>
    </div>

In the DOM, expands to this HTML:

<my-button _ngcontent-ng-c1665131262="" icon="info" variant="flat" iconposition="end">

  <button
    class="my-button my-button--basic my-button--medium my-button--flat my-button--icon my-button--icon-end tpcn-typography-button"
    id="null" type="button">
    <div class="my-button-surface"></div>
    <div class="my-button-state my-button-hover"></div>
    <div class="my-button-state my-button-pressed"></div>
    <div class="my-button-state my-button-selected"></div>
    <div class="my-button-state my-button-focus"></div>
    <div class="my-button-content"><!--container--><!--container--><!--container--><!--container--><tpcn-icon
        class="my-button-icon" _nghost-ng-c2485891322="">
        <div _ngcontent-ng-c2485891322="" svgicon="" class="tpcn-icon"><svg fill="currentColor" viewBox="0 0 24 24">
            <path fill="none" d="M0 0h24v24H0V0z"></path>
            <path
              d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 15c-.55 0-1-.45-1-1v-4c0-.55.45-1 1-1s1 .45 1 1v4c0 .55-.45 1-1 1zm0-8c-.55 0-1-.45-1-1s.45-1 1-1 1 .45 1 1-.45 1-1 1z">
            </path>
          </svg></div><!--container--><!--container-->
      </tpcn-icon><!--container-->
    </div>
  </button>
</my-button>

Questions:

  1. How do I make this work ?
  2. How can I look up my settings button in that array, via settingsButtons.find() ?

** UPDATE** This ended up working for us. We ended up using the .app-controls class which wrapped our custom buttons.

it('should toggle showSettings when settings button is clicked', () => {    
    const settingSpy = jest.spyOn(component as any, 'showSettings');
    const buttons = fixture.debugElement.queryAll(By.css('.app-controls button'));

    buttons[0].triggerEventHandler('click', null);
    fixture.detectChanges();
    expect(settingSpy).toHaveBeenCalled();
  });


Solution

  • You are clicking the actual component, which will not do anything, you need to query for the button whose (click) event triggers the buttonClick event emitter.

    The selector can be changed to:

    fixture.debugElement.queryAll(By.css('.app-controls button'));
    ...
    buttons[0].triggerEventHandler('click', null);