0
votes

I have written a very simple test which is expect component to be truthy. it was working but as soon I put a SetTimeOut in oninit it started failing with below.

Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.

describe('AppComponent', () => {
  let appComponent: AppComponent;
  let fixture: ComponentFixture<AppComponent>;

  beforeEach(async(() => {
    var errorMessages = new Array<ErrorMessage>();
    errorMessages.push(new ErrorMessage(500));

    TestBed.configureTestingModule({
      declarations: [
        AppComponent
      ],
      imports: [
        RouterTestingModule,
        MessagesModule
      ],
      providers: [
        { provide: AuthService, useValue: TestHelper.createAuthServiceSpy() },
        { provide: ErrorHandlingService, useValue: TestHelper.createErrorHandlingServiceSpy(errorMessages) }
      ]
    }).compileComponents();

    fixture = TestBed.createComponent(AppComponent);
    appComponent = fixture.debugElement.componentInstance;
    fixture.detectChanges();
  }));

  it('should create the app', async(() => {
    expect(appComponent).toBeTruthy();
  }));

  it('should show error in case of failure', async(() => {
    expect(appComponent.errorMessage.length).toBe(1);
  }));

  it('should get username for loggedin user', async(() => {
    expect(appComponent.username).toBe("username");
  }));
});

All 3 of them fails and when i commented SetTimeOut it started passing.

export class AppComponent implements OnInit {
    username: string = "";
    errorMessage: Message[] = [];
    showError: boolean = false;
    private activatedRoute: ActivatedRoute;
    constructor(public authService: AuthService, private renderer: Renderer, private errorhandlingService: ErrorHandlingService, private router: Router, private titleService: Title) {
        localStorage.removeItem(AppConstants.authenticationLocalStorageKey);
    }

    ngOnInit(): void {
        this.router.events.subscribe(event => {
            if (event instanceof NavigationEnd) {
                var title = this.getTitle(this.router.routerState, this.router.routerState.root).join('-');
                this.titleService.setTitle(title);
            }
        });

        this.errorhandlingService.getErrors().subscribe(errorMessages => {
            let errorMessage: ErrorMessage = errorMessages.pop();
            this.errorMessage = errorMessage ? [{ severity: 'error', summary: `Error Code: ${errorMessage.statusCode}`, detail: `(${errorMessage.text})` }] : [];
            this.renderer.setElementProperty(document.body, "scrollTop", 0);
            this.showError = true;
            setTimeout(() => {
                this.showError = false;
            }, AppConstants.errorMessageFadeTime);
        });
        this.authService.getUsername().subscribe(data => this.username = data
            , error => this.errorhandlingService.handleError(error, 'could not get username'));
    }

    onDeactivate() {
        //scroll to top of page after routing
        this.renderer.setElementProperty(document.body, "scrollTop", 0);
    }

    private getTitle(state, parent) {
        var data = [];
        if (parent && parent.snapshot.data && parent.snapshot.data.title) {
            data.push(parent.snapshot.data.title);
        }

        if (state && parent) {
            data.push(... this.getTitle(state, state.firstChild(parent)));
        }
        return data;
    }


}

below is html

<div>
        <div class="center-text">
            <div class="errorMessage" *ngIf="showError"><p-messages [value]="errorMessage"></p-messages></div>
            <router-outlet (deactivate)="onDeactivate()"></router-outlet>
        </div>
    </div>

I tried fakesync and done but still getting same error, any suggestion?

2

2 Answers

0
votes

I would suggest you to create a fake class as below,

let errorOccured = false;

class FakeAuthService {
    getErrors(){
     return Observable.of(errorOccured);
    }
}

  providers: [
    { provide: AuthService, useClass: FakeAuthService},

  ]

Inject it to the TestBed beforeEach by

authService = TestBed.get(AuthService);

declare a variable as in beforeEach async

let authService : AuthService

Update 1 :

Have the setTimeOut logic inside the completion event of the subscription as

this.errorhandlingService.getErrors().subscribe(errorMessages => {
    let errorMessage: ErrorMessage = errorMessages.pop();
    this.errorMessage = errorMessage ? [{ severity: 'error', summary: `Error Code: ${errorMessage.statusCode}`, detail: `(${errorMessage.text})` }] : [];
    this.renderer.setElementProperty(document.body, "scrollTop", 0);
    this.showError = true;

},error=>{},

()=>{
    setTimeout(() => {
        this.showError = false;
    }, AppConstants.errorMessageFadeTime);
});
0
votes

Assuming the second section of code is in your actual service and not the tests:

the async callback provided by Angular looks and waits for asynchronous code like Observables, Promises and setTimeouts. It tries to resolve the component-under-test's method calls before checking the answer.

Jasmine itself has a configuration option called DEFAULT_TIMEOUT_INTERVAL. In asynchronous tests (just using jasmine, not Angular's test tools), you can pass a method called done() into an it block and Jasmine will wait a certain interval for that method to be called. This is to test asynchronous tests. The default timeout is 5 seconds, so if you're calling a timeout of 5000, that might not be enough time for Jasmine to check because of setTimeout execution shenanigans.

Angular's async wraps the Jasmine done() in a nice little call that will execute the tests after async functions have completed.

Either way, you would only want to use async in tests that are asynchronous. If a test doesn't have an async result, don't make the test wait for one.

In your case, you can set a beforeEach block for the async tests and either wrap your asserts in a timeout greater than 5000, or you can change the jasmine config for just those tests:

... previous tests

describe('timeout tests', () => {

  beforeEach(() => {
    jasmine.DEFAULT_TIMEOUT_INTERVAL = 5250;
  });

  it('should display error for five seconds', async(() => {
     ...tests
  }));

});
... more tests