Je continue à avoir "le stockage local n'est pas défini" dans les tests Jest, ce qui est logique mais quelles sont mes options? Frapper les murs de briques.
Excellente solution de @chiedo
Cependant, nous utilisons la syntaxe ES2015 et j’ai pensé que c’était un peu plus propre de l’écrire de cette façon.
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;
Vous l'avez compris avec l'aide de: https://groups.google.com/forum/#!topic/jestjs/9EPhuNWVYTg
Configurez un fichier avec le contenu suivant:
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 });
Ensuite, vous ajoutez la ligne suivante à votre package.json sous vos configurations Jest
"setupTestFrameworkScriptFile":"PATH_TO_YOUR_FILE",
Si vous utilisez create-react-app, il existe une solution simple et expliquée dans la documentation .
Créez src/setupTests.js
et mettez ceci dedans:
const localStorageMock = {
getItem: jest.fn(),
setItem: jest.fn(),
clear: jest.fn()
};
global.localStorage = localStorageMock;
Tom Mertz contribution dans un commentaire ci-dessous:
Vous pouvez ensuite tester que les fonctions de votre localStorageMock sont utilisées en effectuant quelque chose comme:
expect(localStorage.getItem).toBeCalledWith('token')
// or
expect(localStorage.getItem.mock.calls.length).toBe(1)
à l'intérieur de vos tests si vous voulez vous assurer qu'il s'appelle. Découvrez https://facebook.github.io/jest/docs/en/mock-functions.html } _
ou vous prenez juste un faux paquet comme ceci:
https://www.npmjs.com/package/jest-localstorage-mock
il gère non seulement la fonctionnalité de stockage, mais vous permet également de vérifier si le magasin a été appelé.
Une meilleure alternative qui gère les valeurs undefined
(elle n'a pas toString()
) et renvoie null
si la valeur n'existe pas. Testé avec react
v15, redux
et redux-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
Actuellement (janvier 19), le stockage local ne peut pas être moqué ni épié comme le ferait une blague, comme vous le feriez habituellement, et comme indiqué dans la documentation de création-réaction-application. Cela est dû aux modifications apportées dans jsdom. Vous pouvez lire à ce sujet ici https://github.com/facebook/jest/issues/6798 et ici https://github.com/jsdom/jsdom/issues/2318 .
Pour contourner le problème, vous pouvez également espionner le prototype:
// 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();
Comme @ ck4 a suggéré documentation explique clairement l'utilisation de localStorage
en plaisanterie. Cependant, les fonctions fictives n’exécutaient aucune des méthodes localStorage
.
Vous trouverez ci-dessous un exemple détaillé du composant my react qui utilise des méthodes abstraites pour écrire et lire des données,
//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 };
Erreur:
TypeError: _setupLocalStorage2.default.setItem is not a function
Réparer:
Ajouter ci-dessous une fonction fictive pour plaisanterie (chemin: .jest/mocks/setUpStore.js
)
let mockStorage = {};
module.exports = window.localStorage = {
setItem: (key, val) => Object.assign(mockStorage, {[key]: val}),
getItem: (key) => mockStorage[key],
clear: () => mockStorage = {}
};
Snippet est référencé depuis ici
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);
});
});
Créer une maquette et l'ajouter à l'objet global
Riffé quelques autres réponses ici pour le résoudre pour un projet avec TypeScript. J'ai créé un LocalStorageMock comme ceci:
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]
}
}
Ensuite, j'ai créé une classe LocalStorageWrapper que j'utilise pour tous les accès au stockage local de l'application au lieu d'accéder directement à la variable de stockage local global. Facilite la mise en place de la maquette dans l'emballage pour les tests.
J'ai trouvé cette solution de 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
});
Vous pouvez insérer ce code dans vos setupTests et cela devrait fonctionner correctement.
Je l'ai testé dans un projet avec typesctipt.
Si vous recherchez une maquette et non un talon, voici la solution que j'utilise:
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
J'exporte les éléments de stockage pour une initialisation facile. C'EST À DIRE. Je peux facilement le définir sur un objet
Dans les versions les plus récentes de Jest + JSDom, il n'est pas possible de le définir, mais le stockage local est déjà disponible et vous pouvez l'espionner comme suit:
const setItemSpy = jest.spyOn(Object.getPrototypeOf(window.localStorage), 'setItem');
Vous devez espionner le prototype de l'objet Storage.
const spy = jest.spyOn(Storage.prototype, 'getItem');
Et après cela pour l'affirmer comme:
expect(spy).toHaveBeenCalledWith(somevalue);
La solution suivante est compatible pour les tests avec une configuration plus stricte de TypeScript, ESLint, TSLint et 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 pour savoir comment mettre à jour global.localStorage