web-dev-qa-db-ja.com

JavaScript単体テストでlocalStorageをモックする方法は?

localStorageをモックするライブラリはありますか?

私は Sinon.JS を他のほとんどのJavaScriptのモック作成に使用してきましたが、本当に素晴らしいことがわかりました。

私の最初のテストでは、localStorageがfirefox(sadface)での割り当てを拒否していることが示されているため、おそらくこれについて何らかのハックが必要になるでしょう:/

現在の私のオプションは(私が見ているように)次のとおりです。

  1. すべてのコードが使用するラッピング関数を作成し、それらをモックします
  2. LocalStorageの何らかの種類の(複雑な場合がある)状態管理(テストの前のスナップショットlocalStorage、クリーンアップ復元スナップショット)を作成します。
  3. ??????

あなたはこれらのアプローチをどう思いますか、これについて他にもっと良い方法があると思いますか?いずれにしても、オープンソースの良さのために、最終的に作成した「ライブラリ」をgithubに配置します。

87
Anthony Sottile

ジャスミンでそれをモックする簡単な方法を次に示します。

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

すべてのテストでローカルストレージをモックする場合は、テストのグローバルスコープで上記のbeforeEach()関数を宣言します(通常の場所はspecHelper.jsスクリプトです) 。

117

必要に応じて、グローバルlocalStorage/sessionStorage(同じAPIを使用)をモックするだけです。
例えば:

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

そして実際にあなたがすることはそのようなものです:

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

また、オブジェクトのコンストラクター関数に依存関係を注入するオプションも検討してください。

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)

モックと単体テストに沿って、ストレージ実装のテストを避けたいです。たとえば、アイテムの設定後にストレージの長さが増加したかどうかをチェックしても意味がありません。

実際のlocalStorageオブジェクトのメソッドを置き換えることは明らかに信頼できないため、「ダム」mockStorageを使用し、必要に応じて次のような個々のメソッドをスタブ化します。

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

これが私がすることです...

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

localStorageをモックするライブラリはありますか?

私はちょうど1つを書きました:

(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

})();

私の最初のテストでは、localStorageがfirefoxでの割り当てを拒否していることが示されています

グローバルコンテキストでのみ。上記のラッパー関数を使用すると、問題なく機能します。

6

現在のソリューションはFirefoxでは機能しません。これは、localStorageがhtml仕様で変更不可として定義されているためです。ただし、localStorageのプロトタイプに直接アクセスすることでこれを回避できます。

クロスブラウザソリューションは、Storage.prototype上のオブジェクトをモックすることです。

代わりにspyOn(localStorage、 'setItem')を使用

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

bzbarskyおよびteogeosの応答から取得 https ://github.com/jasmine/jasmine/issues/299

5
roo2

次に、sinon spyとmockを使用した例を示します。

// 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
Manuel Bitto

localStorage dataプロパティを書き込み不可かつ設定不可として宣言しているため、一部の回答で示唆されているように、グローバルwindowオブジェクトのlocalStorageプロパティを上書きしても、ほとんどのJSエンジンでは機能しません。 。

しかし、少なくともPhantomJS(バージョン1.9.8)のWebKitバージョンでは、レガシーAPIを使用できることがわかりました__defineGetter__localStorageにアクセスした場合の動作を制御します。それでも、これが他のブラウザーでも機能する場合は興味深いでしょう。

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

このアプローチの利点は、テストしようとしているコードを変更する必要がないことです。

4
Conrad Calmez

ストレージオブジェクトを使用する各メソッドにストレージオブジェクトを渡す必要はありません。代わりに、ストレージアダプターに触れる任意のモジュールの構成パラメーターを使用できます。

古いモジュール

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

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

構成「ラッパー」機能を備えた新しいモジュール

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

コードのテストでモジュールを使用する場合

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

MockStorageクラスは次のようになります

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

実動コードでモジュールを使用する場合は、代わりに実際のlocalStorageアダプターを渡します

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

ライブラリとして再利用しやすくするために、Pumbaa80の回答に対するコメントを別の回答として繰り返すことを決めました。

私はPumbaa80のコードを取り、それを少し改良し、テストを追加し、npmモジュールとしてここに公開しました: https://www.npmjs.com/package/mock-local-storage

ソースコードを次に示します。 https://github.com/letsrock-today/mock-local-storage/blob/master/src/mock-localstorage.js

いくつかのテスト: https://github.com/letsrock-today/mock-local-storage/blob/master/test/mock-localstorage.js

モジュールはグローバルオブジェクト(定義されているウィンドウまたはグローバル)にモックlocalStorageおよびsessionStorageを作成します。

私の他のプロジェクトのテストでは、次のようにmochaを使用する必要がありました。mocha -r mock-local-storageは、テスト中のすべてのコードでグローバル定義を使用できるようにします。

基本的に、コードは次のようになります。

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

Object.definePropertyを介して追加されたすべてのメソッドは、通常のアイテムとして反復、アクセス、または削除されず、長さがカウントされないことに注意してください。また、アイテムがオブジェクトに配置されるときに呼び出されるコールバックを登録する方法を追加しました。このコールバックは、テストでクォータ超過エラーをエミュレートするために使用できます。

1

これは私がそれをやりたい方法です。シンプルに保ちます。

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

    });
  });

私はそれをm笑する必要がないことがわかりました。実際のローカルストレージをsetItemを介して必要な状態に変更し、値をクエリしてgetItemを介して変更されたかどうかを確認できます。何回変更されたかを見ることができないので、モックほど強力ではありませんが、私の目的には役立ちました。

0
RandomEngy

残念ながら、テストシナリオでlocalStorageオブジェクトをモックできる唯一の方法は、テストするコードを変更することです。コードを匿名関数でラップし(とにかく実行する必要があります)、「依存性注入」を使用して、ウィンドウオブジェクトへの参照を渡す必要があります。何かのようなもの:

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

次に、テスト内で次を指定できます。

window.mockWindow = { localStorage: { ... } };
0
John Kurlak