Odpowiedzi:
Świetne rozwiązanie od @chiedo
Jednak używamy składni ES2015 i czułem, że pisanie tego w ten sposób było trochę czystsze.
class LocalStorageMock {
constructor() {
this.store = {};
}
clear() {
this.store = {};
}
getItem(key) {
return this.store[key] || null;
}
setItem(key, value) {
this.store[key] = value.toString();
}
removeItem(key) {
delete this.store[key];
}
};
global.localStorage = new LocalStorageMock;
|| null, dlatego mój test się nie powiódł, ponieważ w moim teście używałem not.toBeDefined(). Rozwiązanie @Chiedo sprawi, że znowu zadziała
Rozgryzłem to z pomocą: https://groups.google.com/forum/#!topic/jestjs/9EPhuNWVYTg
Skonfiguruj plik o następującej zawartości:
var localStorageMock = (function() {
var store = {};
return {
getItem: function(key) {
return store[key];
},
setItem: function(key, value) {
store[key] = value.toString();
},
clear: function() {
store = {};
},
removeItem: function(key) {
delete store[key];
}
};
})();
Object.defineProperty(window, 'localStorage', { value: localStorageMock });
Następnie dodaj następujący wiersz do pliku package.json w ramach konfiguracji Jest
"setupTestFrameworkScriptFile":"PATH_TO_YOUR_FILE",
"setupFiles": [...]działa również. Z opcją array pozwala na rozdzielenie mocków na osobne pliki. Np .:"setupFiles": ["<rootDir>/__mocks__/localStorageMock.js"]
getItemróżni się nieznacznie od wartości zwracanej przez przeglądarkę, jeśli brak danych zostanie ustawiony dla określonego klucza. wywołanie, getItem("foo")gdy nie jest ustawione, na przykład zwróci nullw przeglądarce, ale undefinedprzez ten próbny - to powodowało niepowodzenie jednego z moich testów. Prostym rozwiązaniem dla mnie był powrót store[key] || nullw getItemfunkcji
localStorage['test'] = '123'; localStorage.getItem('test')
W przypadku korzystania z aplikacji create-react-app istnieje prostsze i bardziej zrozumiałe rozwiązanie opisane w dokumentacji .
Utwórz src/setupTests.jsi umieść w nim:
const localStorageMock = {
getItem: jest.fn(),
setItem: jest.fn(),
clear: jest.fn()
};
global.localStorage = localStorageMock;
Wkład Toma Mertza w komentarzu poniżej:
Następnie możesz sprawdzić, czy funkcje localStorageMock są używane, wykonując coś takiego jak
expect(localStorage.getItem).toBeCalledWith('token')
// or
expect(localStorage.getItem.mock.calls.length).toBe(1)
jeśli chcesz się upewnić, że został wywołany. Sprawdź https://facebook.github.io/jest/docs/en/mock-functions.html
localStorageczego używasz w swoim kodzie. (jeśli używasz create-react-appi wszystkich automatycznych skryptów, które zapewnia naturalnie)
expect(localStorage.getItem).toBeCalledWith('token')lub expect(localStorage.getItem.mock.calls.length).toBe(1)wewnątrz testów, jeśli chcesz się upewnić, że został wywołany. Zajrzyj na facebook.github.io/jest/docs/en/mock-functions.html
localStorage? Czy nie chciałbyś zresetować szpiegów po każdym teście, aby zapobiec „przenikaniu” do innych testów?
Obecnie (październik '19) localStorage nie można wyszydzać ani szpiegować żartem, jak to zwykle bywa, jak opisano w dokumentach aplikacji create-react-app. Wynika to ze zmian wprowadzonych w jsdom. Możesz o tym przeczytać w żartach i jsdom issue trackers.
Aby obejść ten problem, możesz zamiast tego szpiegować prototyp:
// does not work:
jest.spyOn(localStorage, "setItem");
localStorage.setItem = jest.fn();
// works:
jest.spyOn(window.localStorage.__proto__, 'setItem');
window.localStorage.__proto__.setItem = jest.fn();
// assertions as usual:
expect(localStorage.setItem).toHaveBeenCalled();
jest.spyOn(window.localStorage.__proto__, 'setItem');
lub po prostu bierzesz przykładową paczkę, taką jak ta:
https://www.npmjs.com/package/jest-localstorage-mock
obsługuje nie tylko funkcjonalność magazynu, ale także pozwala przetestować, czy sklep został faktycznie wywołany.
Lepsza alternatywa, która obsługuje undefinedwartości (których nie ma toString()) i zwraca, nulljeśli wartość nie istnieje. Przetestowano to w reactwersji 15 reduxiredux-auth-wrapper
class LocalStorageMock {
constructor() {
this.store = {}
}
clear() {
this.store = {}
}
getItem(key) {
return this.store[key] || null
}
setItem(key, value) {
this.store[key] = value
}
removeItem(key) {
delete this.store[key]
}
}
global.localStorage = new LocalStorageMock
removeItem: developer.mozilla.org/en-US/docs/Web/API/Storage/removeItem
Jeśli szukasz makiety a nie stubu, oto rozwiązanie, którego używam:
export const localStorageMock = {
getItem: jest.fn().mockImplementation(key => localStorageItems[key]),
setItem: jest.fn().mockImplementation((key, value) => {
localStorageItems[key] = value;
}),
clear: jest.fn().mockImplementation(() => {
localStorageItems = {};
}),
removeItem: jest.fn().mockImplementation((key) => {
localStorageItems[key] = undefined;
}),
};
export let localStorageItems = {}; // eslint-disable-line import/no-mutable-exports
Eksportuję elementy pamięci w celu łatwej inicjalizacji. IE Mogę łatwo ustawić go na obiekt
W nowszych wersjach Jest + JSDom nie można tego ustawić, ale lokalna pamięć jest już dostępna i można ją szpiegować w następujący sposób:
const setItemSpy = jest.spyOn(Object.getPrototypeOf(window.localStorage), 'setItem');
Znalazłem to rozwiązanie z github
var localStorageMock = (function() {
var store = {};
return {
getItem: function(key) {
return store[key] || null;
},
setItem: function(key, value) {
store[key] = value.toString();
},
clear: function() {
store = {};
}
};
})();
Object.defineProperty(window, 'localStorage', {
value: localStorageMock
});
Możesz wstawić ten kod w swoim setupTests i powinien działać dobrze.
Przetestowałem to w projekcie z typem.
Niestety rozwiązania, które tu znalazłem, nie zadziałały.
Więc szukałem problemów z Jest GitHub i znalazłem ten wątek
Najbardziej pozytywnymi rozwiązaniami były te:
const spy = jest.spyOn(Storage.prototype, 'setItem');
// or
Storage.prototype.getItem = jest.fn(() => 'bla');
windowlub nie są Storagezdefiniowane. Może to starsza wersja Jest, której używam.
Jak sugerował @ ck4, dokumentacja zawiera jasne wyjaśnienie użycia localStorageżartu. Jednak funkcje pozorowane nie mogły wykonać żadnej z localStoragemetod.
Poniżej znajduje się szczegółowy przykład mojego komponentu reagowania, który wykorzystuje abstrakcyjne metody zapisu i odczytu danych,
//file: storage.js
const key = 'ABC';
export function readFromStore (){
return JSON.parse(localStorage.getItem(key));
}
export function saveToStore (value) {
localStorage.setItem(key, JSON.stringify(value));
}
export default { readFromStore, saveToStore };
Błąd:
TypeError: _setupLocalStorage2.default.setItem is not a function
Fix:
Dodaj poniżej funkcji makiety jest (dla ścieżki: .jest/mocks/setUpStore.js)
let mockStorage = {};
module.exports = window.localStorage = {
setItem: (key, val) => Object.assign(mockStorage, {[key]: val}),
getItem: (key) => mockStorage[key],
clear: () => mockStorage = {}
};
Odwołanie do fragmentu jest stąd
Odsunęłam tutaj kilka innych odpowiedzi, aby rozwiązać problem dla projektu za pomocą Typescript. Utworzyłem LocalStorageMock w następujący sposób:
export class LocalStorageMock {
private store = {}
clear() {
this.store = {}
}
getItem(key: string) {
return this.store[key] || null
}
setItem(key: string, value: string) {
this.store[key] = value
}
removeItem(key: string) {
delete this.store[key]
}
}
Następnie utworzyłem klasę LocalStorageWrapper, której używam do uzyskiwania dostępu do lokalnej pamięci w aplikacji zamiast bezpośredniego dostępu do globalnej zmiennej lokalnej pamięci. Ułatwiono umieszczenie makiety w opakowaniu do testów.
describe('getToken', () => {
const Auth = new AuthService();
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Ik1yIEpvc2VwaCIsImlkIjoiNWQwYjk1Mzg2NTVhOTQ0ZjA0NjE5ZTA5IiwiZW1haWwiOiJ0cmV2X2pvc0Bob3RtYWlsLmNvbSIsInByb2ZpbGVVc2VybmFtZSI6Ii9tcmpvc2VwaCIsInByb2ZpbGVJbWFnZSI6Ii9Eb3Nlbi10LUdpci1sb29rLWN1dGUtbnVrZWNhdDMxNnMtMzExNzAwNDYtMTI4MC04MDAuanBnIiwiaWF0IjoxNTYyMzE4NDA0LCJleHAiOjE1OTM4NzYwMDR9.YwU15SqHMh1nO51eSa0YsOK-YLlaCx6ijceOKhZfQZc';
beforeEach(() => {
global.localStorage = jest.fn().mockImplementation(() => {
return {
getItem: jest.fn().mockReturnValue(token)
}
});
});
it('should get the token from localStorage', () => {
const result = Auth.getToken();
expect(result).toEqual(token);
});
});
Utwórz makietę i dodaj ją do globalobiektu
Musisz mockować pamięć lokalną za pomocą tych fragmentów
// localStorage.js
var localStorageMock = (function() {
var store = {};
return {
getItem: function(key) {
return store[key] || null;
},
setItem: function(key, value) {
store[key] = value.toString();
},
clear: function() {
store = {};
}
};
})();
Object.defineProperty(window, 'localStorage', {
value: localStorageMock
});
A w żartobliwej konfiguracji:
"setupFiles":["localStorage.js"]
Nie wahaj się zapytać o wszystko.
Następujące rozwiązanie jest kompatybilne do testowania z bardziej rygorystyczną konfiguracją TypeScript, ESLint, TSLint i Prettier { "proseWrap": "always", "semi": false, "singleQuote": true, "trailingComma": "es5" }:
class LocalStorageMock {
public store: {
[key: string]: string
}
constructor() {
this.store = {}
}
public clear() {
this.store = {}
}
public getItem(key: string) {
return this.store[key] || undefined
}
public setItem(key: string, value: string) {
this.store[key] = value.toString()
}
public removeItem(key: string) {
delete this.store[key]
}
}
/* tslint:disable-next-line:no-any */
;(global as any).localStorage = new LocalStorageMock()
HT / https://stackoverflow.com/a/51583401/101290, aby dowiedzieć się, jak zaktualizować global.localStorage
Aby zrobić to samo w skrypcie, wykonaj następujące czynności:
Skonfiguruj plik o następującej zawartości:
let localStorageMock = (function() {
let store = new Map()
return {
getItem(key: string):string {
return store.get(key);
},
setItem: function(key: string, value: string) {
store.set(key, value);
},
clear: function() {
store = new Map();
},
removeItem: function(key: string) {
store.delete(key)
}
};
})();
Object.defineProperty(window, 'localStorage', { value: localStorageMock });
Następnie dodaj następujący wiersz do pliku package.json w ramach konfiguracji Jest
"setupTestFrameworkScriptFile":"PATH_TO_YOUR_FILE",
Lub importujesz ten plik w swoim przypadku testowym, w którym chcesz mockować lokalny magazyn.
To zadziałało dla mnie,
delete global.localStorage;
global.localStorage = {
getItem: () =>
}
value + ''w