I have a StorageService
wrapper for @ionic/storage-angular
where I have a function that looks roughly like this:
import { Injectable } from '@angular/core';
import { Storage } from '@ionic/storage-angular';
@Injectable({
providedIn: 'root'
})
export class StorageService {
constructor(public storage: Storage) {}
async getData() {
const data = [];
await this.initStorage();
return new Promise((resolve) => {
this.storage
.forEach((value, key) => {
if (key.startsWith('data-')) {
data.push({
key: 'someKey,
value,
});
}
})
.then(() => resolve(data));
});
}
// other functions here
}
I have a unit test that looks like this
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { fakeAsync, flush, TestBed } from '@angular/core/testing';
import { StorageService } from './storage.service';
import { Storage } from '@ionic/storage-angular';
describe('StorageService', () => {
let service: StorageService;
let mockStorage: jasmine.SpyObj<Storage>;
beforeEach(() => {
mockStorage = jasmine.createSpyObj('Storage', ['get', 'set', 'remove', 'create']);
TestBed.configureTestingModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
providers: [
{ provide: Storage, useValue: mockStorage },
],
});
service = TestBed.inject(StorageService);
});
describe('getData()', () => {
it('should call StorageService.initStorage()', fakeAsync(() => {
spyOn<any>(service, 'initStorage').and.stub();
service.getData();
flush();
expect(service['initStorage']).toHaveBeenCalled();
}));
});
// other tests here
});
It fails with
Error: Uncaught (in promise): TypeError: _this.storage.forEach is not a function
TypeError: _this.storage.forEach is not a function
Storage itself is supposed to be iterable yet jasmine doesn't see it as such because of the way I mocked it, I assume.
The question is: how do I unit test this bit correctly? I don't want to create a real storage instance for testing purposes, is there a way to define this.storage as iterable via mocks?
CodePudding user response:
It seems that Storage
is an array.
Try this for a quick unblock:
providers: [
{ provide: Storage, useValue: [] },
],
We are providing an empty array for Storage
because we are doing this.storage.forEach(
. You can modify this array to your liking.
CodePudding user response:
Answering my own questions again.
My goal was to both preserve my spy object to mock storage.get
and storage.set
and be able to test storage.forEach
. That doesn't seem to be possible. The answer above may suit those who only need to test storage.forEach
.
I resorted to not using storage.forEach
at all and instead I iterate through storage.keys
like this:
async getData(): Promise<Array<{key: string, value}>> {
await this.initStorage();
return this.storage.keys()
.then(keys => Promise.all(keys
.filter(key => key.startsWith('data-'))
.map(async key => {
const value = await this.storage.get(key);
return {
key: 'someKey',
value,
}
})
));
}
This yields the same result and is much easier to test. I just needed to add the keys method to my existing spy like this:
mockStorage = jasmine.createSpyObj('Storage', ['get', 'set', 'remove', 'create', 'keys]);
and then mock the return value
mockStorage.keys.and.resolveTo(['different', 'storage', 'keys']);
It's rather straightforward from there. Hope this helps someone in the future.