JavaScript birim testlerinde localStorage ile nasıl dalga geçilir?


103

Dışarıda dalga geçilecek kütüphane var localStoragemı?

Diğer javascript alaylarımın çoğu için Sinon.JS kullanıyorum ve gerçekten harika olduğunu gördüm .

İlk testlerim localStorage'ın firefox'ta (sadface) atanmayı reddettiğini gösteriyor, bu yüzden muhtemelen bunun etrafında bir çeşit hacklemeye ihtiyacım olacak: /

Şu andaki seçeneklerim (gördüğüm gibi) aşağıdaki gibidir:

  1. Tüm kodumun kullandığı ve bunlarla alay eden sarmalama işlevleri oluşturun
  2. LocalStorage için bir tür (karmaşık olabilir) durum yönetimi (test öncesi anlık görüntü localStorage, temizleme geri yükleme anlık görüntüsü) oluşturun.
  3. ??????

Bu yaklaşımlar hakkında ne düşünüyorsunuz ve bunu yapmanın daha iyi yolları olduğunu düşünüyor musunuz? Her iki durumda da sonuçta ortaya çıkan "kütüphaneyi" açık kaynak iyiliği için github'a koyacağım.


34
# 4'ü kaçırdınız:Profit!
Chris Laplante

Yanıtlar:


128

İşte Jasmine'le dalga geçmenin basit bir yolu:

beforeEach(function () {
  var store = {};

  spyOn(localStorage, 'getItem').andCallFake(function (key) {
    return store[key];
  });
  spyOn(localStorage, 'setItem').andCallFake(function (key, value) {
    return store[key] = value + '';
  });
  spyOn(localStorage, 'clear').andCallFake(function () {
      store = {};
  });
});

Tüm testlerinizde yerel depolamayla dalga geçmek istiyorsanız, beforeEach()yukarıda gösterilen işlevi testlerinizin genel kapsamında bildirin (olağan yer bir specHelper.js betiğidir).


1
+1 - bunu sinon ile de yapabilirsiniz. İşin sırrı, tüm localStorage nesnesiyle dalga geçmeye uğraşmanın nedenidir, sadece ilgilendiğiniz yöntemlerle (getItem ve / veya setItem) alay edin.
s1mm0t


4
Bir ReferenceError: localStorage is not defined(FB Jest ve npm kullanarak testleri çalıştırıyorum)… nasıl çalışılacağına dair bir fikrim var mı?
FeifanZ

1
window.localStorage
Gözetlemeyi

22
andCallFakeand.callFakeyasemin 2'de değiştirildi . +
Venugopal

51

sadece ihtiyaçlarınız için global localStorage / sessionStorage ile alay edin (aynı API'ye sahiptirler).
Örneğin:

 // Storage Mock
  function storageMock() {
    let storage = {};

    return {
      setItem: function(key, value) {
        storage[key] = value || '';
      },
      getItem: function(key) {
        return key in storage ? storage[key] : null;
      },
      removeItem: function(key) {
        delete storage[key];
      },
      get length() {
        return Object.keys(storage).length;
      },
      key: function(i) {
        const keys = Object.keys(storage);
        return keys[i] || null;
      }
    };
  }

Ve sonra aslında yaptığınız şey şudur:

// mock the localStorage
window.localStorage = storageMock();
// mock the sessionStorage
window.sessionStorage = storageMock();

1
Öneriyi düzenle: değer olmadığında getItemdöndürülmelidir :; nullreturn storage[key] || null;
cyberwombat

8
2016 itibariyle, bu modern tarayıcılarda çalışmıyor gibi görünüyor (Chrome ve Firefox kontrol edildi); localStoragebir bütün olarak geçersiz kılmak mümkün değildir.
jakub.g

2
Evet, maalesef bu artık işe yaramıyor, ama storage[key] || nullbunun yanlış olduğunu da iddia ediyorum . Eğer storage[key] === 0o dönecektir nullyerine. return key in storage ? storage[key] : nullYine de yapabileceğini düşünüyorum .
redbmk

Bunu sadece SO'da kullandım! Bir cazibe gibi çalışıyor - sadece gerçek bir sunucu üzerindeyken localStor'u tekrar localStorage'a function storageMock() { var storage = {}; return { setItem: function(key, value) { storage[key] = value || ''; }, getItem: function(key) { return key in storage ? storage[key] : null; }, removeItem: function(key) { delete storage[key]; }, get length() { return Object.keys(storage).length; }, key: function(i) { var keys = Object.keys(storage); return keys[i] || null; } }; } window.localStor = storageMock();
çevirmek zorundasınız

2
@ a8m 10.15.1'e güncelleme düğümünden sonra hata alıyorum TypeError: Cannot set property localStorage of #<Window> which has only a getter, bunu nasıl düzeltebilirim?
Tasawer Nawaz

19

Ayrıca, bir nesnenin yapıcı işlevine bağımlılıkları enjekte etme seçeneğini de göz önünde bulundurun.

var SomeObject(storage) {
  this.storge = storage || window.localStorage;
  // ...
}

SomeObject.prototype.doSomeStorageRelatedStuff = function() {
  var myValue = this.storage.getItem('myKey');
  // ...
}

// In src
var myObj = new SomeObject();

// In test
var myObj = new SomeObject(mockStorage)

Alay ve birim testine paralel olarak, depolama uygulamasını test etmekten kaçınmayı seviyorum. Örneğin, bir öğeyi vb. Ayarladıktan sonra depolama süresinin arttığını kontrol etmenin bir anlamı yok.

Yöntemleri gerçek localStorage nesnesindeki değiştirmek açıkça güvenilmez olduğundan, "aptal" bir mockStorage kullanın ve tek tek yöntemleri istediğiniz gibi saplayın, örneğin:

var mockStorage = {
  setItem: function() {},
  removeItem: function() {},
  key: function() {},
  getItem: function() {},
  removeItem: function() {},
  length: 0
};

// Then in test that needs to know if and how setItem was called
sinon.stub(mockStorage, 'setItem');
var myObj = new SomeObject(mockStorage);

myObj.doSomeStorageRelatedStuff();
expect(mockStorage.setItem).toHaveBeenCalledWith('myKey');

1
Bu soruya bakmayalı epey zaman geçtiğini fark ettim - ama aslında bunu yapmaya başladım.
Anthony Sottile

1
Zaman içinde kırılma riski bu kadar yüksek olmadığı için bu tek değerli çözümdür.
oligofren

14

Bu benim işim...

var mock = (function() {
  var store = {};
  return {
    getItem: function(key) {
      return store[key];
    },
    setItem: function(key, value) {
      store[key] = value.toString();
    },
    clear: function() {
      store = {};
    }
  };
})();

Object.defineProperty(window, 'localStorage', { 
  value: mock,
});

13

Mevcut çözümler Firefox'ta çalışmayacaktır. Bunun nedeni localStorage'ın html spesifikasyonu tarafından değiştirilemez olarak tanımlanmasıdır. Bununla birlikte, localStorage'ın prototipine doğrudan erişerek bunu aşabilirsiniz.

Çapraz tarayıcı çözümü, Storage.prototypeörn.

yerine spyOn (localStorage 'setItem') kullanımı

spyOn(Storage.prototype, 'setItem')
spyOn(Storage.prototype, 'getItem')

bzbarsky ve teogeos'un buradaki yanıtlarından alınmıştır https://github.com/jasmine/jasmine/issues/299


1
Yorumunuz daha fazla beğeni almalıdır. Teşekkür ederim!
LorisBachert

6

Dışarıda dalga geçilecek kütüphane var localStoragemı?

Ben sadece bir tane yazdım:

(function () {
    var localStorage = {};
    localStorage.setItem = function (key, val) {
         this[key] = val + '';
    }
    localStorage.getItem = function (key) {
        return this[key];
    }
    Object.defineProperty(localStorage, 'length', {
        get: function () { return Object.keys(this).length - 2; }
    });

    // Your tests here

})();

İlk testlerim localStorage'ın firefox'ta atanabilir olmayı reddettiğini gösteriyor

Yalnızca küresel bağlamda. Yukarıdaki gibi bir sarmalayıcı işlevi ile gayet iyi çalışıyor.


1
ayrıca kullanabilirsinizvar window = { localStorage: ... }
user123444555621

1
Ne yazık ki bu, ihtiyaç duyacağım ve pencere nesnesine eklemiş olacağım her özelliği bilmem gerektiği anlamına geliyor (ve prototipini kaçırıyorum vb.). JQuery'nin ihtiyaç duyabileceği her şey dahil. Ne yazık ki bu bir çözüm değil gibi görünüyor. Ayrıca, testler kullanılan test kodudur localStorage, testlerin localStoragedoğrudan içlerinde olması gerekmez. Bu çözüm, localStoragediğer betikler için olanı değiştirmez, dolayısıyla çözüm değildir.
Kapsam

1
Test edilebilir hale getirmek için kodunuzu uyarlamanız gerekebilir. Bunun çok can sıkıcı olduğunu biliyorum ve bu yüzden birim testler yerine ağır selenyum testini tercih ediyorum.
user123444555621

Bu geçerli bir çözüm değil. Bu anonim işlevin içinden herhangi bir işlevi çağırırsanız, sahte pencereye veya yerelStorage nesnesine yönelik başvuruyu kaybedersiniz. Birim testinin amacı, bir dış işlev çağırmanızdır. Dolayısıyla localStorage ile çalışan işlevinizi çağırdığınızda, taklidi kullanmayacaktır. Bunun yerine, test ettiğiniz kodu anonim bir işleve sarmalısınız. Test edilebilir hale getirmek için, pencere nesnesini bir parametre olarak kabul etmesini sağlayın.
John Kurlak

Bu modelde bir hata var: Var olmayan bir öğeyi alırken, getItem null döndürmelidir. Sahte olarak, tanımsız döndürür. Doğru kod olmalıdırif this.hasOwnProperty(key) return this[key] else return null
Evan

4

İşte sinon casusu ve alayını kullanan bir örnek:

// window.localStorage.setItem
var spy = sinon.spy(window.localStorage, "setItem");

// You can use this in your assertions
spy.calledWith(aKey, aValue)

// Reset localStorage.setItem method    
spy.reset();



// window.localStorage.getItem
var stub = sinon.stub(window.localStorage, "getItem");
stub.returns(aValue);

// You can use this in your assertions
stub.calledWith(aKey)

// Reset localStorage.getItem method
stub.reset();

4

Üzerine yazmak localStorageküresel özelliği windowbazı yanıtlar çoğu JS motorlarda çalışmaz onlar beyan çünkü önerildiği gibi nesne localStoragedeğil yazılabilir ve yapılandırılabilir olarak değil veri özelliği.

Bununla birlikte, en azından PhantomJS'nin (sürüm 1.9.8) WebKit sürümüyle , erişilirse __defineGetter__ne olacağını kontrol etmek için eski API'yi kullanabileceğinizi öğrendim localStorage. Yine de, bunun diğer tarayıcılarda da çalışması ilginç olurdu.

var tmpStorage = window.localStorage;

// replace local storage
window.__defineGetter__('localStorage', function () {
    throw new Error("localStorage not available");
    // you could also return some other object here as a mock
});

// do your tests here    

// restore old getter to actual local storage
window.__defineGetter__('localStorage',
                        function () { return tmpStorage });

Bu yaklaşımın yararı, test etmek üzere olduğunuz kodu değiştirmeniz gerekmeyecek olmasıdır.


Bunun PhantomJS 2.1.1'de çalışmayacağını fark ettim. ;)
Conrad Calmez

4

Depolama nesnesini, onu kullanan her yönteme iletmeniz gerekmez. Bunun yerine, depolama adaptörüne dokunan herhangi bir modül için bir yapılandırma parametresi kullanabilirsiniz.

Eski modülünüz

// hard to test !
export const someFunction (x) {
  window.localStorage.setItem('foo', x)
}

// hard to test !
export const anotherFunction () {
  return window.localStorage.getItem('foo')
}

Yapılandırma "sarmalayıcı" işlevine sahip yeni modülünüz

export default function (storage) {
  return {
    someFunction (x) {
      storage.setItem('foo', x)
    }
    anotherFunction () {
      storage.getItem('foo')
    }
  }
}

Modülü test kodunda kullandığınızda

// import mock storage adapater
const MockStorage = require('./mock-storage')

// create a new mock storage instance
const mock = new MockStorage()

// pass mock storage instance as configuration argument to your module
const myModule = require('./my-module')(mock)

// reset before each test
beforeEach(function() {
  mock.clear()
})

// your tests
it('should set foo', function() {
  myModule.someFunction('bar')
  assert.equal(mock.getItem('foo'), 'bar')
})

it('should get foo', function() {
  mock.setItem('foo', 'bar')
  assert.equal(myModule.anotherFunction(), 'bar')
})

MockStorageSınıf aşağıdaki gibi görünebilir

export default class MockStorage {
  constructor () {
    this.storage = new Map()
  }
  setItem (key, value) {
    this.storage.set(key, value)
  }
  getItem (key) {
    return this.storage.get(key)
  }
  removeItem (key) {
    this.storage.delete(key)
  }
  clear () {
    this.constructor()
  }
}

Modülünüzü üretim kodunda kullanırken bunun yerine gerçek localStorage adaptörünü iletin

const myModule = require('./my-module')(window.localStorage)

millet, bu sadece es6'da geçerlidir: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… (ama harika bir çözüm ve her yerde bulunmasını bekleyemem!)
Alex Moore- Niemi

@ AlexMoore-Niemi burada ES6'nın çok az kullanımı var. Hepsi çok az değişiklikle ES5 veya daha düşük bir sürüm kullanılarak yapılabilir.
Teşekkürler

evet, sadece export default functionböyle bir argümana sahip bir modülü işaret edip başlatmak sadece es6'dır. desen ne olursa olsun duruyor.
Alex Moore-Niemi

Huh? requireBir modülü içe aktarmak ve aynı ifadedeki bir argümana uygulamak için eski stili kullanmam gerekiyordu . Bunu ES6'da yapmanın bildiğim bir yolu yok. Aksi takdirde ES6 kullanırdımimport
Teşekkürler

2

Pumbaa80'in cevabına yaptığım yorumu ayrı bir cevap olarak tekrar etmeye karar verdim, böylece onu bir kütüphane olarak yeniden kullanmak daha kolay olacak.

Pumbaa80'in kodunu aldım, biraz geliştirdim, testler ekledim ve burada bir npm modülü olarak yayınladım: https://www.npmjs.com/package/mock-local-storage .

İşte bir kaynak kodu: https://github.com/letsrock-today/mock-local-storage/blob/master/src/mock-localstorage.js

Bazı testler: https://github.com/letsrock-today/mock-local-storage/blob/master/test/mock-localstorage.js

Modül, global nesne üzerinde sahte localStorage ve sessionStorage oluşturur (pencere veya global, hangisi tanımlıdır).

Diğer projemin testlerinde bunu mocha ile gerekli kıldım: mocha -r mock-local-storagetest edilen tüm kodlar için genel tanımları kullanılabilir hale getirmek için.

Temel olarak, kod aşağıdaki gibi görünür:

(function (glob) {

    function createStorage() {
        let s = {},
            noopCallback = () => {},
            _itemInsertionCallback = noopCallback;

        Object.defineProperty(s, 'setItem', {
            get: () => {
                return (k, v) => {
                    k = k + '';
                    _itemInsertionCallback(s.length);
                    s[k] = v + '';
                };
            }
        });
        Object.defineProperty(s, 'getItem', {
            // ...
        });
        Object.defineProperty(s, 'removeItem', {
            // ...
        });
        Object.defineProperty(s, 'clear', {
            // ...
        });
        Object.defineProperty(s, 'length', {
            get: () => {
                return Object.keys(s).length;
            }
        });
        Object.defineProperty(s, "key", {
            // ...
        });
        Object.defineProperty(s, 'itemInsertionCallback', {
            get: () => {
                return _itemInsertionCallback;
            },
            set: v => {
                if (!v || typeof v != 'function') {
                    v = noopCallback;
                }
                _itemInsertionCallback = v;
            }
        });
        return s;
    }

    glob.localStorage = createStorage();
    glob.sessionStorage = createStorage();
}(typeof window !== 'undefined' ? window : global));

Aracılığıyla eklenen tüm yöntemlerin Object.definePropertyyinelenmeyeceğini, erişilmeyeceğini veya normal öğeler olarak kaldırılmayacağını ve uzunluk olarak sayılmayacağını unutmayın. Ayrıca, bir öğe nesneye konulmak üzereyken çağrılan geri aramayı kaydetmenin bir yolunu ekledim. Bu geri arama, testlerde kota aşımı hatasını taklit etmek için kullanılabilir.


2

Onunla dalga geçmeme gerek olmadığını anladım. Gerçek yerel depolamayı, üzerinden istediğim duruma değiştirebilir setItem, ardından üzerinden değişip değişmediğini görmek için değerleri sorgulayabilirdim getItem. Bir şeyin kaç kez değiştiğini göremediğiniz kadar alay etmek kadar güçlü değil, ama benim amaçlarım için işe yaradı.


0

Ne yazık ki, bir test senaryosunda localStorage nesnesiyle dalga geçmenin tek yolu, test ettiğimiz kodu değiştirmektir. Kodunuzu anonim bir işleve (yine de yapıyor olmanız gerekir) sarmanız ve pencere nesnesine bir başvuru iletmek için "bağımlılık ekleme" kullanmanız gerekir. Gibi bir şey:

(function (window) {
   // Your code
}(window.mockWindow || window));

Ardından, testinizin içinde şunları belirtebilirsiniz:

window.mockWindow = { localStorage: { ... } };

0

Ben böyle yapmaktan hoşlanıyorum. Basit tutar.

  let localStoreMock: any = {};

  beforeEach(() => {

    angular.mock.module('yourApp');

    angular.mock.module(function ($provide: any) {

      $provide.service('localStorageService', function () {
        this.get = (key: any) => localStoreMock[key];
        this.set = (key: any, value: any) => localStoreMock[key] = value;
      });

    });
  });

0

https://medium.com/@armno/til-mocking-localstorage-and-sessionstorage-in-angular-unit-tests-a765abdc9d87 için krediler Sahte bir yerel depolama yapın ve caleld olduğunda yerel depolama hakkında casusluk yapın

 beforeAll( () => {
    let store = {};
    const mockLocalStorage = {
      getItem: (key: string): string => {
        return key in store ? store[key] : null;
      },
      setItem: (key: string, value: string) => {
        store[key] = `${value}`;
      },
      removeItem: (key: string) => {
        delete store[key];
      },
      clear: () => {
        store = {};
      }
    };

    spyOn(localStorage, 'getItem')
      .and.callFake(mockLocalStorage.getItem);
    spyOn(localStorage, 'setItem')
      .and.callFake(mockLocalStorage.setItem);
    spyOn(localStorage, 'removeItem')
      .and.callFake(mockLocalStorage.removeItem);
    spyOn(localStorage, 'clear')
      .and.callFake(mockLocalStorage.clear);
  })

Ve burada kullanıyoruz

it('providing search value should return matched item', () => {
    localStorage.setItem('defaultLanguage', 'en-US');

    expect(...
  });
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.