Angular Testing: Get HTML Element inside child component that uses ng-content

Viewed 2681

I have a problem writing unit tests for a component that uses ngb-accordion to display its content. In this case, the accordion is only used for styling purposes, but I use it to provide design consitency across the app.

The most simplest template I could come up with to show the error:

<ngb-accordion #acc="ngbAccordion" activeIds="sample">
  <ngb-panel id="sample">
    <ng-template ngbPanelContent>
      <p class="myClass">Some Text</p> <!-- I want to access this element -->
    </ng-template>
  </ngb-panel>
</ngb-accordion>

The testcase looks as follows:

it('should find my element', () => {
    fixture = TestBed.createComponent(LoginComponent);

    component = fixture.componentInstance;
    fixture.detectChanges();
    const p = fixture.debugElement.query(By.css('.myClass')).nativeElement;
    p.id;
});

which throws an error:

TypeError: Cannot read property 'nativeElement' of null

Here is the beforeEach for my test:

beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [ LoginComponent, NgbAccordion ],
      providers : [FormBuilder, ...]
    })
    .compileComponents();
  }));

(where LoginComponent is the tested Component).

I dont think it has something to do with ng-template, as with the following html, the element is found correctly:

<div *ngIf="false else always">Should not show</div>

<ng-template #always>
  <p class="myClass">Some Text</p>
</ng-template>

I don't know why it doesn't work with ngbAccordion, could it be a timing issue? As I am not mocking the NgbAccordion class (and I dont want to!), I expect it's contents to be rendered correctly. Is it, because the html is probably rendered with 'ng-content' from within the 'NgbAccordion'? If so, what could I do to access my element?

Any help is greatly appreciated.

2 Answers

This sometimes happens to me too when it's inside of foreign elements like that.

Instead of query try going straight to the nativeElement or using document.querySelector.

 const p = fixture.nativeElement.querySelector('.myClass');
 console.log(p); // see if it returns
 const p2 = document.querySelector('.myClass');
 console.log(p2); // see if it returns

If that doesn't work, inspect the nativeElement and ensure the element is there;

const nativeElement = fixture.nativeElement;
console.log(nativeElement);

Edit: I am not entirely sure what the directive's class name is (maybe NgBPanelContent) but ensure that you have it in your declarations array so the HTML will know how to paint it when it sees that directive.

I suppose you need to wait for the rendering;

// async added here
it('should find my element', async () => {
    fixture = TestBed.createComponent(LoginComponent);

    component = fixture.componentInstance;
    fixture.detectChanges();
    // and await rendering
    await fixture.whenRenderingDone();

    const p = fixture.debugElement.query(By.css('.myClass')).nativeElement;
    p.id;
});
Related