How do I unit test if an element is visible when the *ngIf directive is used using Jasmine in Angular

58,318

Solution 1

If the element is hidden, then it wont be rendered inside the dom.

You can check

expect(fixture.debugElement.query(By.css('.header'))).toBeUndefined();

EDIT : toBeNull() works better in the above case

expect(fixture.debugElement.query(By.css('.header'))).toBeNull();

And also you have a syntax error while fetching the button element. nativeElement is not a function.

Change it this way :

const button = fixture.debugElement.query(By.css('button')).nativeElement;

Solution 2

When testing if a component is being shown or not using ngIf I try to get the element (in this case you use, i.e., debugElement.query(By.css('.header')).nativeElement) and if it should be shown I expect it to be truthy, otherwise falsy.

Something like this:

it('should hide contents if show is false', () => {
    // should be rendered initially
    expect(debugElement.query(By.css('.header')).nativeElement).toBeTruthy();
    //trigger change
    const button = debugElement.query(By.css('button')).nativeElement;
    button.click();   // this will change show to false
    fixture.detectChanges();
    // should not be rendered
    expect(debugElement.query(By.css('.header')).nativeElement).toBeFalsy();
});

Also, bear in mind that sometimes you need to use ComponentFixture#whenStable to detect when the fixture is stable like this:

  it('should hide contents if show is false', () => {
    const fixture = TestBed.createComponent(AppComponent);
    fixture.whenStable().then(() => {
      // same test code here
    });
  });

See this working test for a component which resembles this scenario.

See [GitHub repository]

Solution 3

You need add fixture.detectChanges() after you change value of 'show' to true

component.show = true;
fixture.detectChanges()

Solution 4

Writing test case for *ngIf condition use toBeNull condition.

Try with below code, it works for me.

expect(fixture.debugElement.query(By.css('.header'))).toBeNull();

Solution 5

When using ngIf, angular completely removes the node from markup. So you need to check that this element not exists.

Documentation says:

ngIf evaluates the expression and then renders the then or else template in its place when expression is truthy or falsy respectively.

To be more precise, it's just not rendered

Share:
58,318
J-man
Author by

J-man

Developer working in JavaScript, Java, C#, VB.NET, WinForms, WPF, ASP.NET and SharePoint. I am attempting to shift my mindset into a more functional programming mindset and utilizing test-driven development. Web developer using and interested in HTML, CSS, Angular, Vue.js, Python, JavaScript, jQuery, Graphic Design, Responsive Web Design, content analysis

Updated on February 02, 2021

Comments

  • J-man
    J-man over 3 years

    I have an Angular 6 app and writing some unit tests trying to determine if an element is visible or not based solely on the boolean result of an *ngIf directive.

    Markup:

    <div class="header" *ngIf="show">
        <div>...</div>
    </div>
    

    spec file:

    it('should hide contents if show is false', () => {
        const button = debugElement.query(By.css('button')).nativeElement;
        button.click();   // this will change show to false
        fixture.detectChanges();
        expect(debugElement.query(By.css('.header')).nativeElement.style.hidden).toBe(true);
    });
    

    I can't seem to get the hidden attribute from the div. Does angular use another approach to hiding the element from the DOM using the *ngIf directive? Do I need to get another property from the nativeElement?

    Thanks!

  • J-man
    J-man almost 6 years
    thanks! I also tried to test to see if the nativeElement is undefined and it keeps failing the test saying Expected HTMLNode to be undefined so it seems to be checking the value of the node rather than whether or not it exists in the DOM
  • Drag13
    Drag13 almost 6 years
    @J-man check fixture.debugElement.query(By.css('.header')).nativeElement
  • J-man
    J-man almost 6 years
    Ultimately what worked for me was to check the node itself as you did in the first line of this answer. Interestingly, however, instead of checking undefined I ended up checking whether or not the node was null using the toBeNull() function. That seemed to work for me.
  • joecracker
    joecracker about 5 years
    Be careful with combining fixture.debugElement.query with invocations of expect(...). This can cause Out-Of-Memory errors in the browser. Happened to me. See stackoverflow.com/a/48606192/2042301
  • Michel
    Michel almost 4 years
    I had the same question, and this was indeed the correct answer. However, we have (for some reason) not the default change detection: ChangeDetectionStrategy.OnPush.. It turned out in the browser the element was removed automatically, but in the unit test I had to call the `ChangeDetectorRef.detectChanges()' manually in order to get it to work
  • Jim
    Jim over 3 years
    The code here seems to pass for me even when the ngIf evaluates to false...
  • Werek
    Werek about 3 years
    Shouldn't the second example test function be marked as async since we're working with promises there?
  • paulroho
    paulroho almost 3 years
    @Werek - As we are not awaiting the promise, but utilizing then to head over to callback-world, the function signature does not have to be marked with async.
  • MikhailRatner
    MikhailRatner about 2 years
    Thank you! In my case, I had an Observable and thus couldn't assign it to be true. I had to mock the observable and assign it to the component.thePropertyOnWhichTheConditionDepends. The fixture.detectChanges() was the final bit what made the test work!