5

I'm upgrading our Angular2 app to use rc4 and I started getting an error on my unit tests:

Cannot use setInterval from within an async zone test

My widget makes a request for data from its ngOnInit method and puts up a loading indicator while that request is made. My mocked service returns some data after 1ms.

Here's a simplified version that exposes the problem

import { inject, async, TestComponentBuilder, ComponentFixture} from '@angular/core/testing';
import {Http, Headers, RequestOptions, Response, HTTP_PROVIDERS} from '@angular/http';
import {provide, Component} from '@angular/core';

import {Observable} from "rxjs/Rx";

class MyService {
    constructor(private _http: Http) {}
    getData() {
        return this._http.get('/some/rule').map(resp => resp.text());
    }
}

@Component({
    template: `<div>
      <div class="loader" *ngIf="_isLoading">Loading</div>
      <div class="data" *ngIf="_data">{{_data}}</div>
    </div>`
})
class FakeComponent {
    private _isLoading: boolean = false;
    private _data: string = '';

    constructor(private _service: MyService) {}

    ngOnInit() {
        this._isLoading = true;
        this._service.getData().subscribe(data => {
            this._isLoading = false;
            this._data = data;
        });
    }
}

describe('FakeComponent', () => {
    var service = new MyService(null);
    var _fixture:ComponentFixture<FakeComponent>;

    beforeEach(async(inject([TestComponentBuilder], (tcb:TestComponentBuilder) => {
        return tcb
            .overrideProviders(FakeComponent, [
                HTTP_PROVIDERS,
                provide(MyService, {useValue: service}),
            ])
            .createAsync(FakeComponent)
            .then((fixture:ComponentFixture<FakeComponent>) => {
                _fixture = fixture;
            });
    })));

    it('Shows loading while fetching data', (cb) => {
        // Make the call to getData take one ms so we can verify its state while the request is pending
        // Error occurs here, when the widget is initialized and sends out an XHR
        spyOn(service, 'getData').and.returnValue(Observable.of('value').delay(1));
        _fixture.detectChanges();
        expect(_fixture.nativeElement.querySelector('.loader')).toBeTruthy();
        // Wait a few ms, should not be loading
        // This doesn't seem to be the problem
        setTimeout(() => {
            _fixture.detectChanges();
            expect(_fixture.nativeElement.querySelector('.loader')).toBeFalsy();
            cb();
        }, 10);
    });
});

This was working fine in Angular2 rc1 and it throws an error in rc4, any suggestions?

Also, there are no errors if you use a setTimeout directly from the test itself

        fit('lets you run timeouts', async(() => {
            setTimeout(() => {
                expect(1).toBe(1);
            }, 10);
        }));
2

2 Answers 2

3

I've found that for some reason, you cannot use a promise created with Observable.of(anything).delay() in a test.

My solution for this was to implement that line myself, which almost makes sense considering the other example posted in the question did work.

// This is what we should be doing, but somehow, it isn't working.
// return Observable.of(result).delay(0));
function createDelayedObservable <T>(result:any, time:number = 0):Observable<T> {
    return new Observable<T>(observer => {
        setTimeout(() =>  observer.next(result), time);
    });
}

However, I still don't understand why the following doesn't fail, so I'm not accepting my own answer in the hopes that someone with a solid understanding of zones can tell me what's happening.

it('should be able to use delay in tests', (cb) => {
    var obs = Observable.of(1).delay(0);
    obs.subscribe(val => {
        expect(val).toBe(1);
        cb()
    });
});
Sign up to request clarification or add additional context in comments.

1 Comment

I have a similar problem i want to test a component that contains a this.scroll = Observable.fromEvent(document.querySelector('nav'), 'scroll').debounceTime(100).subscribe((event) => { this.check(); }); but since it's a component I have not idea where I would place the callback
2

I've encountered the same issue. And I was able to workaround it using the jasmine done parameter.

fit('lets you run timeouts', (done) => {
    async(() => {
        setTimeout(() => {
            expect(1).toBe(1);
            done();
        }, 10);
    });
});

2 Comments

Thanks, but the example you copied doesn't cause the problem for me.
Also, adding a callback to the first example in my question doesn't solve the problem. That's how I actually had it earlier, before I found out how async worked.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.