Pertanyaan Bagaimana cara mencemooh localStorage dalam tes unit JavaScript?


Apakah ada perpustakaan di luar sana untuk mengejek localStorage?

Saya telah menggunakan Sinon.JS untuk sebagian besar javascript saya yang lain dan telah menemukan itu benar-benar hebat.

Pengujian awal saya menunjukkan bahwa localStorage menolak untuk dapat ditetapkan di firefox (sadface) jadi saya mungkin perlu semacam peretasan di sekitar ini: /

Pilihan saya seperti yang sekarang (seperti yang saya lihat) adalah sebagai berikut:

  1. Buat fungsi pembungkusan yang digunakan semua kode saya dan tirukan itu
  2. Buat semacam manajemen negara (mungkin rumit) (snapshot localStorage sebelum tes, dalam membersihkan memulihkan snapshot) untuk localStorage.
  3. ??????

Apa pendapat Anda tentang pendekatan ini dan apakah menurut Anda ada cara lain yang lebih baik untuk melakukan hal ini? Bagaimanapun saya akan menempatkan "perpustakaan" yang dihasilkan yang akhirnya saya buat di github untuk kebaikan open source.


75
2017-07-14 16:24


asal


Jawaban:


Berikut ini cara sederhana untuk mengejeknya dengan Jasmine:

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 = {};
  });
});

Jika Anda ingin mencemooh penyimpanan lokal di semua tes Anda, nyatakan beforeEach() fungsi yang ditunjukkan di atas dalam lingkup global pengujian Anda (tempat yang biasa adalah a specHelper.js naskah).


102
2018-01-17 15:04



hanya mencemooh localStorage / sessionStorage global (mereka memiliki API yang sama) untuk kebutuhan Anda.
Sebagai contoh:

 // Storage Mock
  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;
      }
    };
  }

Dan kemudian apa yang sebenarnya Anda lakukan, adalah sesuatu seperti itu:

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

38
2017-10-03 11:07



Juga pertimbangkan opsi untuk menyuntikkan dependensi dalam fungsi konstruktor objek.

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)

Sejalan dengan pengujian mengejek dan unit, saya ingin menghindari pengujian penerapan penyimpanan. Misalnya tidak ada gunanya memeriksa apakah lama penyimpanan meningkat setelah Anda mengatur item, dll.

Karena jelas tidak dapat diandalkan untuk mengganti metode pada objek localStorage nyata, gunakan "bodoh" mockStorage dan rintis metode individu yang diinginkan, seperti:

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');

17
2017-11-22 20:04



Apakah ada perpustakaan di luar sana untuk mengejek localStorage?

Saya baru saja menulis satu:

(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

})();

Pengujian awal saya menunjukkan bahwa localStorage menolak untuk dapat ditetapkan di firefox

Hanya dalam konteks global. Dengan fungsi pembungkus seperti di atas, itu berfungsi dengan baik.


7
2017-07-14 18:34



Inilah yang saya lakukan...

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,
});

6
2018-03-03 10:11



Berikut ini contoh menggunakan mata-mata sinon dan tiruan:

// 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
2018-03-02 14:12



Anda tidak harus melewatkan objek penyimpanan ke setiap metode yang menggunakannya. Sebagai gantinya, Anda dapat menggunakan parameter konfigurasi untuk setiap modul yang menyentuh adaptor penyimpanan.

Modul lama Anda

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

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

Modul baru Anda dengan fungsi "pembungkus" config

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

Ketika Anda menggunakan modul dalam kode pengujian

// 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')
})

Itu MockStorage kelas mungkin terlihat seperti ini

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()
  }
}

Saat menggunakan modul Anda dalam kode produksi, lewati adaptor localStorage yang sebenarnya

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

4
2017-11-07 19:03



Menimpa localStorage milik global window objek seperti yang disarankan dalam beberapa jawaban tidak akan bekerja di sebagian besar mesin JS, karena mereka menyatakan localStorage properti data tidak dapat ditulis dan tidak dapat dikonfigurasi.

Namun saya menemukan bahwa setidaknya dengan versi WebKit PhantomJS (versi 1.9.8) Anda dapat menggunakan API warisan __defineGetter__ untuk mengendalikan apa yang terjadi jika localStorage diakses. Masih akan menarik jika ini bekerja di browser lain juga.

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 });

Manfaat dari pendekatan ini adalah Anda tidak perlu memodifikasi kode yang akan Anda uji.


2
2018-02-10 15:18