Search code examples
angularunit-testingangular5angular6angular-unit-test

Angular 6 Unit Test ngOnInit with a setTimeOut not working


I have a component with a setTimeOut function inside a ngOnInit function. To write unit test cases for that I'm using tick and fakeAsync to fast forward the setTimeOut. But, it's not getting executed which in turn is not calling other function closeAlert().

Component code:

export class BannerComponent implements OnInit {

  @Input()errorData: any;

  @Input()callback: any;

  @Input()autoHide: boolean;

  constructor() { }

  ngOnInit() {

    if (this.autoHide) {
      setTimeout
        (() => {
          this.closeAlert();
        }, 500);
    }
  }

  closeAlert() {
    this.errorData = null;
    if (this.callback) {
      this.callback();
    }
  };
}

Spec file:

describe('BannerComponent', () => {
  let component: BannerComponent;
  let fixture: ComponentFixture<BannerComponent>;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [ BannerComponent ]
    })
    .compileComponents();
  }));

  beforeEach(async() => {
    fixture = TestBed.createComponent(BannerComponent);
    component = fixture.componentInstance;
    component.ngOnInit();
    fixture.detectChanges();
  });

  it("banner should hide after 500ms", fakeAsync(() => {
    component.errorData = {
      _statusMessage: "New alert banner",
      _statusCode: '200',
    };
    component.callback = null;;
    component.autoHide = true;

    tick(600);
    fixture.detectChanges()

    fixture.whenStable().then(() => {
      let banner = fixture.debugElement.query(By.css('.success'));
      expect(banner).toBe(null)
    })
  }));

});

Html code:

<div class="success">
    <p>{{errorData._statusMessage}}</p>
</div>

Solution

  • A couple of issues I saw with the code.

    • You are calling both component.ngOnInit() and fixture.detectChanges() (which will also call ngOnInit) BEFORE you have set up valid data in component.errorData.
    • It is unclear to me why you are expecting banner to be null with the html you have shown. I therefore changed the test to seeing component.closeAlert() had been called, and if component.errorData had been reset to null, since it appears that is what you are really wanting to test. To test for this I spied on component.closeAlert().
    • I set up the ticks to show exactly when the call is made to component.closeAlert() by testing after tick(499), and then after one more tick ...

    Working StackBlitz.

    Code:

    beforeEach(async(() => { // slight correction of incorrect async wrapper ...
      fixture = TestBed.createComponent(BannerComponent);
      component = fixture.componentInstance;
      // component.ngOnInit(); // <-- don't call this here, the data isn't set up yet ...
      // fixture.detectChanges(); // ditto
    }));
    
    it("banner should hide after 500ms", fakeAsync(() => {
      spyOn(component, 'closeAlert').and.callThrough(); // set up a spy so we can test later
      component.errorData = {
        _statusMessage: "New alert banner",
        _statusCode: '200',
      };
      component.callback = null;;
      component.autoHide = true;
    
      fixture.detectChanges(); // <-- this will execute ngOnInit()
      expect(component.errorData).not.toBeNull(); // <-- after ngOnInit, still NOT null
      expect(component.closeAlert).not.toHaveBeenCalled();
      tick(499); // <-- now let 499ms pass ...
      expect(component.errorData).not.toBeNull(); // <-- after all that "fake" time, still NOT null
      expect(component.closeAlert).not.toHaveBeenCalled();
      tick(1); // <-- now tick for just 1 more millisecond ...
      expect(component.errorData).toBeNull(); // <-- now this has become NULL
      expect(component.closeAlert).toHaveBeenCalled(); // <-- and the method was called
      // fixture.whenStable().then(() => {
      //   let banner = fixture.debugElement.query(By.css('.success'));
      //   expect(banner).toBe(null)
      // });
    }));
    

    I hope this helps!