Search code examples
angularjasmineangular-test

Jasmine Testing Constructor That has a Promise


I have a service that acts as a data-store. In it's constructor, it attempts to "hydrate" the data-set from the device's storage (using Ionic and it's Storage service):

@Injectable()
export class SimpleDataStore {

     private _data: BehaviorSubject<any> = new BehaviorSubject<any>(undefined);
     public data: Observable<any> = this._data.asObservable();

     constructor(private _http: HttpClient, private _storage) {
         this.initializeData();
     }

     private initializeData(): void {
          this._storage.get("dataKey")
              .then(data => this._data.next(data))
              .catch(() => this._data.next([]);
     }

}

I know how to write async tests with Jasmine, and how to access private members/methods, as well as knowing to need to check _data.getValue() for my desired result -- but my issue is not knowing how to test:

  1. The constructor, and/or;
  2. initializeData so that it waits for the Promise to finish, since no Promise is being returned in the method.

Thanks for any and all help!


Solution

  • Ideally you would have initializeData() return the promise that it constructs so you can easily wait for it to resolve in your tests. Then instead of trying to figure out when the promise resolves you can simply mock initializeData() before the class is constructed.

    Given that the initializeData() method is changed to return the promise you can test the SimpleDataStore class as follows:

    describe('SimpleDataStore', function () {
    
        beforeEach(function () {
            this.initializeData = spyOn(SimpleDataStore.prototype, 'initializeData');
            this.http = jasmine.createSpyObj('HttpClient', ['get']);
            this.storage = jasmine.createSpyObj('Storage', ['get']);
            this.dataStore = new SimpleDataStore(this.http, this.storage);
    
            // initializeData is called immediately upon construction.
            expect(this.initializeData).toHaveBeenCalled();
        });
    
        describe('initializeData', function () {
    
            beforeEach(function () {
                // The data store is initialized, we can now let the calls go to the real implementation.
                this.initializeData.and.callThrough();
            });
    
            afterEach(function () {
                expect(this.storage.get).toHaveBeenCalledWith('dataKey');
            });
    
            it('is initialized with "dataKey" storage', function (done) {
                const data = {};
                this.storage.get.and.returnValue(Promise.resolve(data));
                this.dataStore.initializeData()
                    .then(() => expect(this.dataStore._data.getValue()).toBe(data))
                    .catch(fail)
                    .finally(done)
            });
    
            it('is initialized with empty array when "dataKey" storage rejects', function (done) {
                this.storage.get.and.returnValue(Promise.reject());
                this.dataStore.initializeData()
                    .then(() => expect(this.dataStore._data.getValue()).toEqual([]))
                    .catch(fail)
                    .finally(done)
            });
    
        });
    
    });