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"]
getItem
róż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 null
w przeglądarce, ale undefined
przez ten próbny - to powodowało niepowodzenie jednego z moich testów. Prostym rozwiązaniem dla mnie był powrót store[key] || null
w getItem
funkcji
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.js
i 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
localStorage
czego używasz w swoim kodzie. (jeśli używasz create-react-app
i 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 undefined
wartości (których nie ma toString()
) i zwraca, null
jeśli wartość nie istnieje. Przetestowano to w react
wersji 15 redux
iredux-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');
window
lub nie są Storage
zdefiniowane. 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 localStorage
metod.
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 global
obiektu
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