3

I have got several of the following test cases which run successfully when executed individually but they randomly fail when run in group. They all use setTimeout. They are in single spec file segregated in separate describe methods.

Eg. This test case (which uses setTimeout) passes when I run it by itself but when I run it in a group, it fails. I suspect the issue has to do something with setTimeout. I tried using done but that isn't solving the issue.

    describe('AppComponent Test suite', () => {

      let component: AppComponent;
      let fixture: ComponentFixture<AppComponent>;

      beforeEach(async(() => {
        TestBed.configureTestingModule({
          declarations: [
            AppComponent,
    ...
          ],
          imports: [
    ....
          ],
          providers: [{provide: APP_BASE_HREF, useValue: '/'},
....]

    }).compileComponents();
  }));


      beforeEach(() => {
        fixture = TestBed.createComponent(AppComponent);
        component = fixture.componentInstance;
        let componentDE = fixture.debugElement;
        let componentNE:HTMLElement = componentDE.nativeElement;
        componentNE.setAttribute("signup","someIncorrectValue");
        fixture.detectChanges();
      });
      it('should show dialog message if the application has unrecognised value of signup attribute in url',(done)=>{
        spyOn(component,'showDialog');
        setTimeout(()=>{
          expect(component.showDialog).toHaveBeenCalledWith("Unrecognised message: someIncorrectValue",jasmine.any);
        },1000); done();
      });
    });

Imagine more similar test cases inthe same file each in its own describe, all using setTimeout.

Why are they failing? If the issue is synchronzation, how do I synchronize them?

UPDATE

I tried async/await as well but no joy!

it('should show dialog message if the application has unrecognised value of signup attribute in url',async ()=>{
    spyOn(component,'showDialog');
    await setTimeout(()=>{
      expect(component.showDialog).toHaveBeenCalledWith("Unrecognised message: someIncorrectValue",jasmine.any);

      },1000);

  });

I also moved the call to done inside setTimeout's callback but no joy there as well.

it('should show dialog message if the application has unrecognised value of signup attribute in url', (done)=>{
    spyOn(component,'showDialog');
     setTimeout(()=>{
      expect(component.showDialog).toHaveBeenCalledWith("Unrecognised message: someIncorrectValue",jasmine.any);
      done();
      },1000);
  });

The component is the entry point of the application and is invoked by passing a signup attribute in the url.

<app-root signup=@signup> 

The signup tells angular at startup whether the app got started because user clicked a signup link. Accordingly, angular can show a message whether the signup was successful or not. Earlier I faced the following issue (Getting ExpressionChangedAfterItHasBeenCheckedError error I update property of a Component from another Component) and to resolve it, I added a setTimeout in the ngAfterViewInit of the app component

ngAfterViewInit(){


    setTimeout(()=>{
      this.checkIfSignupProcess();
      this.authGuard.authGuardError$.subscribe((authGuardContext:AuthGuardContext)=>{
        this.handleAuthGuardContextMessage(authGuardContext);
      });
      this.dialogService.dialogMessage$.subscribe((message:DialogServiceMessageContext)=>{
        console.log("received message from Dialog box service");
        this.handleDialogServiceMessage(message);
      })
    },100);

    /*
    OR
    this.isSignupProcess(); //this will cause ExpressionChangedAfterItHasBeenCheckedError error s the function changes message of DialogComponent but the change detection isn't complete.
    this.cd.detectChanges();
    */
  }

UPDATE 2

I tried using 'tick' but the test still fails, even when run individually.

fit('should show dialog message if the application has unrecognised value of signup attribute in url', ()=>{
    jasmine.clock().install();
    spyOn(component,'showDialog');
    setTimeout(() => {
      console.log("in spec's timeout");
      expect(component.showDialog).toHaveBeenCalledWith("Unrecognised message: someIncorrectValue",jasmine.any);

    },1000);
    jasmine.clock().tick(1001);
    jasmine.clock().uninstall();
     /*setTimeout(()=>{
      expect(component.showDialog).toHaveBeenCalledWith("Unrecognised message: someIncorrectValue",jasmine.any);
      done();
      },1000);*/
  });

The reason for failure is Expected spy showDialog to have been called with [ 'Unrecognised message: someIncorrectValue', Function ] but it was never called.

UPDATE 3

I notice that even though the the timeout value is 100 in my component and 1000 in my specs, the specs timeout expires first!!!

This code from my component which should show the dialog box is called after my spec's code even though the timeout value in component is 100 compared to 10000 in the specs!!

component's timeout

setTimeout(()=>{
      console.log("timeout of component");
      this.checkIfSignupProcess();
    ...,100
}

spec's timeout

fit('should show dialog message if the application has unrecognised value of signup attribute in url', ()=>{
    jasmine.clock().install();
    spyOn(component,'showDialog');
    setTimeout(() => {
      console.log("in spec's timeout 10000");
      expect(component.showDialog).toHaveBeenCalledWith("Unrecognised message: someIncorrectValue",jasmine.any);

    },10000);
    jasmine.clock().tick(10001);
    jasmine.clock().uninstall();
     /*setTimeout(()=>{
      expect(component.showDialog).toHaveBeenCalledWith("Unrecognised message: someIncorrectValue",jasmine.any);
      done();
      },1000);*/
  });
Manu Chadha
  • 15,555
  • 19
  • 91
  • 184

2 Answers2

0

Don't know why they're failing but in order to get rid of setTimeout, you could try to combine callThrough and callFake as follows:

it('should show dialog ...', (done)=>{
    spyOn(component,'showDialog').and.callThrough().and.callFake(() => {
        expect(component.showDialog).toHaveBeenCalledWith("Unrecognised message: someIncorrectValue",jasmine.any);
        done();
    });
});
uminder
  • 23,831
  • 5
  • 37
  • 72
0

In the end, I reorganised the test cases to following (removed setTimeout from specs and called the function under test directly from the spec.

it('should show dialog message if the application has got error signup attribute in url', ()=>{
    spyOn(component,'showDialog')
    component.checkIfSignupProcess();
    expect(component.showDialog).toHaveBeenCalledWith("Error: Signup wasn't successful",new  DialogContext(''));

  })
Manu Chadha
  • 15,555
  • 19
  • 91
  • 184