Search code examples
angularjasmine

Angular 8 unit test directive which includes setTimeout


I have the following simple autofocus directive:

@Directive({
  selector: '[appAutoFocus]',
})
export class AutofocusDirective implements AfterContentInit {

  @Input() public appAutoFocus: boolean;

  public constructor(private el: ElementRef) { }

  public ngAfterContentInit() {
    if (this.appAutoFocus) {
      setTimeout(() => {
        this.el.nativeElement.focus();
      }, 300);
    }
  }
}

I'm now trying to write some simple unit tests, but 2 out of the 3 tests fail.

@Component({
  template: '<input type="text" [appAutoFocus]="true" />'
})
class TestComponent {
  constructor() { }
}

fdescribe('AutoFocusDirective', () => {
  let component: TestComponent;
  let fixture: ComponentFixture<TestComponent>;
  let inputEl: DebugElement;
     beforeEach(() => {
        TestBed.configureTestingModule({
          declarations: [
            TestComponent,
            AutofocusDirective
          ]
        });

        fixture = TestBed.createComponent(TestComponent);
        component = fixture.componentInstance;
        inputEl = fixture.debugElement.query(By.css('input'));

        spyOn(inputEl.nativeElement, 'focus');
        fixture.detectChanges();
      });

      it('should create an instance', () => {
        expect(component).toBeTruthy();
      });

      it('should call the focus event', fakeAsync(() => {
        tick(400);
        fixture.detectChanges();
        fixture.whenStable().then(() => {
         expect(inputEl.nativeElement.focus).toHaveBeenCalled();
        });
      }));

      it('should autofocus the input control', () => {
        const debugEl: DebugElement = fixture.debugElement;
        expect(debugEl.query(By.css('input:focus'))).not.toBe(null);
      });

"Should call the focus event" fails with Spec 'AutoFocusDirective should call the focus event' has no expectations.

"Should autofocus the input control" fails with Expected null not to be null


Solution

  • The only way i could get this to work was to add a setTimeout to the test spec, so it now looks like so:

      it('should call the focus event', (done) => {
        fixture.detectChanges();
        setTimeout(() => {
          expect(inputEl.nativeElement.focus).toHaveBeenCalled();
          done();
        }, 500);
      });
    

    To be honest, it's a garbage solution but i've wasted too much time on this.