जावास्क्रिप्ट यूनिट परीक्षणों में लोकलस्टेज को कैसे मॉक करें?


103

वहाँ किसी भी पुस्तकालयों वहाँ नकली करने के लिए कर रहे हैं localStorage?

मैं अपने अधिकांश अन्य जावास्क्रिप्ट के लिए Sinon.JS का उपयोग कर रहा हूं और पाया है कि यह वास्तव में बहुत अच्छा है।

मेरे शुरुआती परीक्षण से पता चलता है कि लोकलस्टोरीज फ़ायरफ़ॉक्स (सेडफेस) में असाइन होने से इंकार करता है, इसलिए मुझे शायद इसके लिए किसी तरह के हैक की आवश्यकता होगी: /

मेरे विकल्प अब (जैसा कि मैं देख रहा हूं) इस प्रकार हैं:

  1. रैपिंग फ़ंक्शंस बनाएं जो मेरे सभी कोड उपयोग करते हैं और उन का मज़ाक उड़ाते हैं
  2. स्थानीय प्रबंधन के लिए किसी प्रकार की (जटिल हो सकती है) राज्य प्रबंधन (स्नैपचैट स्थानीय टेस्ट से पहले, क्लीनअप रिस्टोर स्नैपशॉट में बनाएं)।
  3. ??????

आप इन दृष्टिकोणों के बारे में क्या सोचते हैं और क्या आपको लगता है कि इस बारे में जाने के लिए कोई और बेहतर तरीका है? किसी भी तरह से मैं परिणामी "लाइब्रेरी" रखूँगा, जिसे मैं ओपन सोर्स अच्छाई के लिए जीथब पर बना रहा हूँ।


34
आपने # 4 को याद किया:Profit!
क्रिस लैपलेंट

जवाबों:


128

यहाँ जैस्मीन के साथ इसका मजाक उड़ाने का एक सरल तरीका है:

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 स्क्रिप्ट है)।


1
+1 - आप इसे पापोन के साथ भी कर सकते हैं। कुंजी यह है कि पूरे लोकलस्टोरेज ऑब्जेक्ट को मॉक करने के लिए बांधने में परेशान क्यों करें, बस उन तरीकों को प्राप्त करें (getItem और / या setItem) जिसमें आप रुचि रखते हैं।
s1mm0t

6
सिर ऊपर: फ़ायरफ़ॉक्स में इस समाधान के साथ एक मुद्दा लगता है: github.com/pivotal/jasmine/issues/299
cthulhu

4
मुझे एक ReferenceError: localStorage is not defined(एफबी जेस्ट और एनपीएम का उपयोग करके परीक्षण चल रहा है) ... कोई भी विचार कैसे काम करना है?
फिफानज

1
जासूसी की कोशिश करेंwindow.localStorage
बेंज

21
andCallFakeand.callFakeचमेली 2 में बदला + +
वेणुगोपाल

51

अपनी आवश्यकताओं के लिए सिर्फ वैश्विक लोकलस्टोरेज / सेशनस्टोरेज (उनके पास एक ही एपीआई है) का मजाक उड़ाएं।
उदाहरण के लिए:

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

और फिर आप वास्तव में क्या करते हैं, कुछ ऐसा है:

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

1
सुझाव संपादित करें: getItemलौटना चाहिए nullजब मूल्य मौजूद नहीं है: return storage[key] || null;;
साइबरबॉम्बैट

8
2016 तक, ऐसा लगता है कि यह आधुनिक ब्राउज़रों (क्रोम और फ़ायरफ़ॉक्स की जाँच) में काम नहीं करता है; localStorageएक पूरे के रूप में ओवरराइड करना संभव नहीं है।
jakub.g

2
हाँ, दुर्भाग्य से यह अब काम नहीं करता है, लेकिन मैं यह भी तर्क दूंगा कि storage[key] || nullयह गलत है। अगर storage[key] === 0इसके बदले लौटेगा null। मुझे लगता है कि आप return key in storage ? storage[key] : nullहालांकि कर सकते थे ।
redbmk

बस एसओ पर इसका इस्तेमाल किया! एक आकर्षण की तरह काम करता है - बस 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();
लोकलस्टोर को लोकलस्टोरेज में

2
@ a8m मुझे अपडेट नोड के बाद 10.15.1 पर त्रुटि हो रही है कि TypeError: Cannot set property localStorage of #<Window> which has only a getter, किसी भी विचार मैं इसे कैसे ठीक कर सकता हूं?
तसव्वर नवाज

19

किसी ऑब्जेक्ट के कंस्ट्रक्टर फ़ंक्शन में निर्भरता को इंजेक्ट करने के विकल्प पर भी विचार करें।

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)

मॉकिंग और यूनिट परीक्षण के अनुरूप, मैं भंडारण कार्यान्वयन के परीक्षण से बचना पसंद करता हूं। उदाहरण के लिए, यदि आप किसी आइटम को सेट करने के बाद स्टोरेज की लंबाई में वृद्धि करते हैं, तो जाँच का कोई मतलब नहीं है।

चूंकि यह स्पष्ट रूप से अविश्वसनीय है कि वास्तविक लोकलस्टोरेज ऑब्जेक्ट पर विधियों को प्रतिस्थापित किया जाए, इसलिए "डंब" मॉकस्टोरेज का उपयोग करें और व्यक्तिगत विधियों को वांछित के रूप में स्टब करें:

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
मुझे एहसास हुआ कि जब से मैंने इस प्रश्न को देखा है, तब से कुछ समय हो गया है - लेकिन यह वास्तव में है कि मैंने क्या किया।
एंथनी Sottile

1
यह एकमात्र योग्य समाधान है, क्योंकि इसमें समय पर टूटने का इतना अधिक जोखिम नहीं है।
ओलिगोफ़्रेन

14

मैं यह करता हूं...

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

12

वर्तमान समाधान फ़ायरफ़ॉक्स में काम नहीं करेंगे। इसका कारण यह है कि स्थानीय स्तर को html युक्ति द्वारा परिभाषित किया जाता है क्योंकि इसे संशोधित नहीं किया जा सकता है। हालाँकि आप सीधे ही लोकलस्टोरेज के प्रोटोटाइप तक पहुँच कर इसे प्राप्त कर सकते हैं।

क्रॉस ब्राउजर सॉल्यूशन Storage.prototypeउदाहरण के लिए ऑब्जेक्ट को मॉक करने के लिए है

स्पाईऑन के बजाय (लोकलस्टोरेज, 'सेट इटेम') का उपयोग करते हैं

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

bzbarsky और teogeos के उत्तरों को यहां से लिया गया है https://github.com/jasmine/jasmine/issues/299


1
आपकी टिप्पणी को अधिक लाइक मिलना चाहिए। धन्यवाद!
LorisBachert 14

6

वहाँ किसी भी पुस्तकालयों वहाँ नकली करने के लिए कर रहे हैं localStorage?

मैंने सिर्फ एक लिखा है:

(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

})();

मेरे प्रारंभिक परीक्षण से पता चलता है कि लोकलस्टोरेज फ़ायरफ़ॉक्स में असाइन करने से मना कर देता है

केवल वैश्विक संदर्भ में। ऊपर के रूप में एक आवरण समारोह के साथ, यह ठीक काम करता है।


1
आप उपयोग कर सकते हैंvar window = { localStorage: ... }
user123444555621

1
दुर्भाग्य से इसका मतलब है कि मुझे हर उस संपत्ति को जानना होगा जिसकी मुझे आवश्यकता होगी और उसने विंडो ऑब्जेक्ट में जोड़ा है (और मुझे इसके प्रोटोटाइप आदि की याद आती है)। जिनमे jQuery की आवश्यकता हो सकती है शामिल है। दुर्भाग्य से यह एक गैर-समाधान की तरह लगता है। ओह, परीक्षण परीक्षण कोड का उपयोग करते हैं localStorage, परीक्षण जरूरी नहीं कि उनमें localStorageसीधे होते हैं। यह समाधान localStorageअन्य लिपियों के लिए परिवर्तित नहीं होता है इसलिए यह एक गैर-समाधान है। हालांकि स्कूप ट्रिक के लिए +1
एंथनी सॉटिले जूल 15'12

1
परीक्षण योग्य बनाने के लिए आपको अपने कोड को अनुकूलित करने की आवश्यकता हो सकती है। मुझे पता है कि यह बहुत कष्टप्रद है, और यही कारण है कि मैं यूनिट परीक्षणों पर भारी सेलेनियम परीक्षण को प्राथमिकता देता हूं।
user123444555621

यह एक वैध समाधान नहीं है। यदि आप उस अनाम फ़ंक्शन के भीतर से किसी फ़ंक्शन को कॉल करते हैं, तो आप मॉक विंडो या मॉक लोकलस्टोरेज ऑब्जेक्ट के संदर्भ को खो देंगे। एक इकाई परीक्षण का उद्देश्य यह है कि आप एक बाहरी फ़ंक्शन को कॉल करते हैं। इसलिए जब आप अपने फ़ंक्शन को कहते हैं जो लोकलस्टोरेज के साथ काम करता है, तो यह मॉक का उपयोग नहीं करेगा। इसके बजाय, आपको उस कोड को लपेटना होगा जिसे आप एक अनाम फ़ंक्शन में परीक्षण कर रहे हैं। इसे परीक्षण योग्य बनाने के लिए, क्या यह विंडो ऑब्जेक्ट को एक पैरामीटर के रूप में स्वीकार करता है।
जॉन कुर्लाक

उस मॉक में एक बग है: जब कोई आइटम मौजूद नहीं होता है, तो उसे वापस लेना चाहिए। मॉक में, यह अपरिभाषित लौटता है। सही कोड होना चाहिएif this.hasOwnProperty(key) return this[key] else return null
इवान

4

यहाँ पापोन स्पाई और मॉक का उपयोग करने की छूट है:

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

कुछ जवाबों में सुझाए localStorageगए वैश्विक windowऑब्जेक्ट की संपत्ति को अधिलेखित करना अधिकांश जेएस इंजनों में काम नहीं करेगा, क्योंकि वे localStorageडेटा संपत्ति को योग्य नहीं घोषित करते हैं और कॉन्फ़िगर करने योग्य नहीं हैं।

हालाँकि मुझे पता चला कि कम से कम PhantomJS (संस्करण 1.9.8) WebKit संस्करण के साथ आप विरासत एपीआई __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 });

इस दृष्टिकोण का लाभ यह है कि आपको उस कोड को संशोधित नहीं करना होगा जिसका आप परीक्षण करने वाले हैं।


बस ध्यान दिया कि यह 2.1.1 PhantomJS में काम नहीं करेगा। ;)
कॉनराड कैलमेज़

4

आपको स्टोरेज ऑब्जेक्ट को प्रत्येक विधि के पास नहीं करना है जो इसका उपयोग करता है। इसके बजाय, आप किसी भी मॉड्यूल के लिए कॉन्फ़िगरेशन पैरामीटर का उपयोग कर सकते हैं जो भंडारण एडाप्टर को छूता है।

आपका पुराना मॉड्यूल

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

उत्पादन कोड में अपने मॉड्यूल का उपयोग करते समय, इसके बजाय वास्तविक लोकलस्टेज एडाप्टर को पास करें

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

लोगों के लिए शुल्क, यह केवल es6 में मान्य है: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… (लेकिन एक महान समाधान है और मैं हर जगह उपलब्ध होने तक इंतजार नहीं कर सकता!)
एलेक्स एनोर- नीमी

@ एलेक्समोर-नीमी यहाँ ES6 का बहुत कम उपयोग है। यह सब बहुत कम परिवर्तनों के साथ ES5 या निम्न का उपयोग करके किया जा सकता है।
धन्यवाद

हां, केवल इंगित करना export default functionऔर एक arg के साथ एक मॉड्यूल को इनिशियलाइज़ करना जैसे कि es6 ही है। पैटर्न की परवाह किए बिना खड़ा है।
एलेक्स मूर-नीमी

है ना? मुझे requireमॉड्यूल को आयात करने और उसी अभिव्यक्ति में एक तर्क पर लागू करने के लिए पुरानी शैली का उपयोग करना पड़ा । ईएस 6 में ऐसा कोई तरीका नहीं है जिसके बारे में मुझे पता हो। नहीं तो मैंने ES6 का उपयोग किया होताimport
थैंक

2

मैंने अपनी टिप्पणी को 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

मॉड्यूल ग्लोबल ऑब्जेक्ट (विंडो या ग्लोबल, उनमें से कौन परिभाषित है) पर मॉक लोकलस्टोरेज और सेशनस्टोरेज बनाता है।

मेरी अन्य परियोजना के परीक्षणों में मुझे इसके साथ मोचा की आवश्यकता थी: 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ताकि वे नियमित आइटम के रूप में पुनरावृत्त, एक्सेस या हटाए नहीं जाएंगे और लंबाई में गणना नहीं करेंगे। इसके अलावा, मैंने कॉलबैक को पंजीकृत करने का एक तरीका जोड़ा, जिसे तब कहा जाता है जब किसी वस्तु को ऑब्जेक्ट में डाला जाना है। यह कॉलबैक परीक्षण में त्रुटि से अधिक कोटा का अनुकरण करने के लिए इस्तेमाल किया जा सकता है।


2

मैंने पाया कि मुझे इसका मजाक उड़ाने की जरूरत नहीं थी। मैं वास्तविक स्थानीय संग्रहण को उस स्थिति में बदल सकता हूं जिसे मैं इसके माध्यम से चाहता था setItem, फिर मानों को यह देखने के लिए क्वेरी करें कि क्या यह बदल गया है getItem। यह उतने शक्तिशाली नहीं है जितना कि मजाक करना क्योंकि आप नहीं देख सकते कि कितनी बार कुछ बदला गया था, लेकिन यह मेरे उद्देश्यों के लिए काम करता है।


0

दुर्भाग्यवश, जिस तरह से हम एक परीक्षण परिदृश्य में लोकलस्टोरेज ऑब्जेक्ट का मजाक उड़ा सकते हैं, वह यह है कि जिस कोड का हम परीक्षण कर रहे हैं उसे बदलना है। आपको अपना कोड एक अनाम फ़ंक्शन (जिसे आप वैसे भी करना चाहिए) में लपेटना होगा और विंडो ऑब्जेक्ट के संदर्भ में पास करने के लिए "निर्भरता इंजेक्शन" का उपयोग करना होगा। कुछ इस तरह:

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

फिर, अपने परीक्षण के अंदर, आप निर्दिष्ट कर सकते हैं:

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

0

यह मुझे ऐसा करना पसंद है। इसे सरल रखता है।

  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 पर क्रेडिट एक स्थानीय फर्जीवाड़ा करें, और स्थानीय लोगों पर जासूसी करें, जब वह कैलीगार्ड हो।

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

और यहां हम इसका उपयोग करते हैं

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

    expect(...
  });
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.