programing

JavaScript 단위 테스트에서 localStorage를 모의하는 방법은 무엇입니까?

nasanasas 2020. 9. 11. 08:08
반응형

JavaScript 단위 테스트에서 localStorage를 모의하는 방법은 무엇입니까?


조롱 할 라이브러리가 localStorage있습니까?

나는 대부분의 다른 자바 스크립트 조롱에 Sinon.JS사용해 왔으며 정말 훌륭하다는 것을 알았습니다.

내 초기 테스트는 localStorage가 firefox (sadface)에서 할당을 거부한다는 것을 보여 주므로 아마도 이것에 대해 일종의 해킹이 필요할 것입니다.

현재 내 옵션은 다음과 같습니다.

  1. 내 모든 코드에서 사용하는 래핑 함수를 만들고 모의
  2. localStorage에 대한 일종의 (복잡 할 수 있음) 상태 관리 (테스트 전 스냅 샷 localStorage, 정리 복원 스냅 샷에서)를 만듭니다.
  3. ??????

이러한 접근 방식에 대해 어떻게 생각하고 이에 대해 더 나은 방법이 있다고 생각하십니까? 어느 쪽이든 저는 오픈 소스의 장점을 위해 github에 최종적으로 만드는 "라이브러리"를 넣을 것입니다.


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

모든 테스트에서 로컬 저장소를 모의하려면 테스트 beforeEach()의 전역 범위에서 위에 표시된 함수를 선언하십시오 (일반적인 위치는 specHelper.js 스크립트입니다).


필요에 따라 전역 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();

또한 개체의 생성자 함수에 종속성을 삽입하는 옵션을 고려하십시오.

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 개체에서 메서드를 교체하는 것은 분명히 신뢰할 수 없기 때문에 "dumb"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');

이것이 제가하는 것입니다...

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

조롱 할 라이브러리가 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

})();

내 초기 테스트는 localStorage가 firefox에서 할당을 거부한다는 것을 보여줍니다.

글로벌 컨텍스트에서만. 위와 같은 래퍼 기능을 사용하면 잘 작동합니다.


현재 솔루션은 Firefox에서 작동하지 않습니다. 이는 localStorage가 html 사양에 의해 수정 불가능한 것으로 정의되기 때문입니다. 그러나 localStorage의 프로토 타입에 직접 액세스하여이 문제를 해결할 수 있습니다.

크로스 브라우저 솔루션은 Storage.prototype들어 객체를 조롱하는 것입니다.

spyOn (localStorage, 'setItem') 대신 사용

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

bzbarskyteogeos 의 답변 에서 가져옴 https://github.com/jasmine/jasmine/issues/299


다음은 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();

일부 답변에서 제안한대로 localStorage전역 window개체 속성을 덮어 쓰는 것은 대부분의 JS 엔진에서 작동하지 않습니다. localStorage데이터 속성을 쓰기 가능하지 않고 구성 할 수 없다고 선언하기 때문 입니다.

그러나 적어도 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 });

이 접근 방식의 이점은 테스트하려는 코드를 수정할 필요가 없다는 것입니다.


저장소 개체를 사용하는 각 메서드에 전달할 필요가 없습니다. 대신 스토리지 어댑터에 닿는 모든 모듈에 대해 구성 매개 변수를 사용할 수 있습니다.

이전 모듈

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

I decided to reiterate my comment to Pumbaa80's answer as separate answer so that it'll be easier to reuse it as a library.

I took Pumbaa80's code, refined it a bit, added tests and published it as an npm module here: https://www.npmjs.com/package/mock-local-storage.

Here is a source code: https://github.com/letsrock-today/mock-local-storage/blob/master/src/mock-localstorage.js

Some tests: https://github.com/letsrock-today/mock-local-storage/blob/master/test/mock-localstorage.js

Module creates mock localStorage and sessionStorage on the global object (window or global, which of them is defined).

In my other project's tests I required it with mocha as this: mocha -r mock-local-storage to make global definitions available for all code under test.

Basically, code looks like follows:

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

Note that all methods added via Object.defineProperty so that them won't be iterated, accessed or removed as regular items and won't count in length. Also I added a way to register callback which is called when an item is about to be put into object. This callback may be used to emulate quota exceeded error in tests.


Unfortunately, the only way we can mock the localStorage object in a test scenario is to change the code we're testing. You have to wrap your code in an anonymous function (which you should be doing anyway) and use "dependency injection" to pass in a reference to the window object. Something like:

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

Then, inside of your test, you can specify:

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

This is how I like to do it. Keeps it simple.

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

    });
  });

조롱 할 필요가 없다는 것을 알았습니다. 실제 로컬 저장소를 원하는 상태로 변경 setItem한 다음 값을 쿼리하여 변경되었는지 확인할 수 있습니다 getItem. 몇 번이나 변경되었는지 알 수없는 것처럼 조롱하는 것만 큼 강력하지는 않지만 제 목적을 위해 작동했습니다.

참고 URL : https://stackoverflow.com/questions/11485420/how-to-mock-localstorage-in-javascript-unit-tests

반응형