У меня есть компонент, который обращается к локальному хранилищу, и я хочу протестировать его с помощью jestJS. Насколько я могу судить, jest не поддерживает вызовы localStorage.
Это компонент, для которого мне нужны тесты:
const NavBar: React.FC = () => {
const history = useHistory();
const handleCLick = () => {
localStorage.clear();
history.push('/login');
};
return (
<div>
<header>
<div className = "banner">
<div className = "container">
<img
className = "icon "
alt = "icon"
title = "icon"
src = {favicon57}
/>
<p>Official website of the Stuff</p>
</div>
</div>
<nav className = "navbar navbar-expand-md navbar-dark fixed-top">
<div className = "container">
<div className = "navbar-header">
<img
className = "logo "
alt = "logo"
title = "Logo"
src = {Blah}
/>
</div>
<button
className = "navbar-toggler"
type = "button"
data-toggle = "collapse"
data-target = "#navbarCollapse"
aria-controls = "navbarCollapse"
aria-expanded = "false"
aria-label = "Toggle navigation"
>
<span className = "navbar-toggler-icon" />
</button>
<div className = "collapse navbar-collapse" id = "navbarCollapse">
<ul className = "navbar-nav ml-auto">
{isTokenAdmin() ? (
<li className = "nav-item">
<a id = "nav-users" className = "nav-link" href = {ADMIN_URL}>
View Users
</a>
</li>
) : (
<div> </div>
)}
{isTokenActive() ? (
<li className = "nav-item">
<a id = "nav-log-out" className = "nav-link" href = {APP_URL}>
Locations
</a>
</li>
) : (
<div> </div>
)}
{isTokenActive() ? (
<li className = "nav-item">
<a
id = "nav-log-out"
className = "nav-link"
href = {LOGIN_URL}
onClick = {() => {
handleCLick();
}}
>
Logout
</a>
</li>
) : (
<div> </div>
)}
</ul>
</div>
</div>
</nav>
</header>
</div>
);
};
export default NavBar;
Как видите, я отображаю кнопки на основе токена, который я сохранил в localStorage. Как бы вы довели это до 100% тестового покрытия?
Обновлено:
Код функций для получения токена:
export const isTokenActive = (): boolean => {
const userToken: string | null = localStorage.getItem('exp');
if (typeof userToken === 'string') {
return new Date().getTime() < Number.parseInt(userToken, 10);
}
return false;
};
export const isTokenAdmin = (): boolean => {
const userToken: string | null = localStorage.getItem('access_token');
if (typeof userToken === 'string') {
const decodedToken: TokenDetails = jwt_decode(userToken);
return decodedToken.authorities[0] === 'ROLE_Administrator';
}
return false;
};
Это потому, что вы пытаетесь получить доступ к localStorage
непосредственно из браузера как к глобальной переменной. Вам нужно либо объявить его в своих глобальных переменных, либо издеваться над ним (некоторые также используют jsdom
, чтобы издеваться над window
объектом)
Только что отредактировал вопрос, чтобы включить код для isTokenAdmin, isTokenActive
Вы правы, что Jest не поддерживает вызовы localStorage. Это не браузер и не реализует localStorage.
Решение состоит в том, чтобы смоделировать вашу собственную поддельную поддержку localStorage, как показано ниже:
браузерMocks.js
const localStorageMock = (function() {
let store = {}
return {
getItem: function(key) {
return store[key] || null
},
setItem: function(key, value) {
store[key] = value.toString()
},
removeItem: function(key) {
delete store[key]
},
clear: function() {
store = {}
}
}
})()
Object.defineProperty(window, 'localStorage', {
value: localStorageMock
})
Конфигурация Jest (может быть внутри вашего package.json)
"jest": {
"setupFiles": [
"<rootDir>/__jest__/browserMocks.js",
Затем, чтобы увидеть, было ли вызвано localstorage, вы можете следить за ним следующим образом:
describe('signOutUser', () => {
it('should sign out a user', async () => {
const spyLoStoRemove = jest.spyOn(localStorage, 'removeItem')
await signOutUser()
expect(spyLoStoRemove).toHaveBeenCalled()
expect(spyLoStoRemove).toHaveBeenCalledTimes(2)
})
})
Насмешка над тем, как @Jonathan Irwin работает в большинстве случаев, но не всегда, поскольку на самом деле она не имитирует структуру локального хранилища.
Это не сработает, например, если вы используете оператор in
, например.
if (!('MY_KEY' in localStorage)) {
return 'something'
}
Это не сработает, так как исходный экземпляр локального хранилища хранит ключи сам по себе, а методы исходят из его прототипа хранилища, такое поведение имитирует классическое наследование, но на самом деле это больше делегирование, чем наследование, мы можем убедиться, что, например, в консоли Chrome: часть прототипа:
В макете @Jonathan Irwin мы храним пары ключ-значение не в экземпляре локального хранилища, а в отдельном внутреннем объекте закрытия, поэтому мы не можем использовать его так же, как исходное локальное хранилище.
Улучшенная версия:
const storagePrototype = {
getItem: function (key) {
return localStorageMock[key] || null;
},
setItem: function (key, value) {
localStorageMock[key] = value.toString();
},
removeItem: function (key) {
delete localStorageMock[key];
},
clear: function () {
Object.keys(fakeLocalStorage).forEach(
(key) => delete localStorageMock[key]
);
},
};
export const localStorageMock = Object.create(storagePrototype);
Object.defineProperty(window, 'localStorage', {
value: localStorageMock
})
Классовая версия:
class Storage {
getItem(key) {
return this[key] || null;
}
setItem(key, value) {
this[key] = value.toString();
}
removeItem(key) {
delete this[key];
}
clear() {
Object.keys(this).forEach((key) => delete this[key]);
}
}
export const localStorageMock = Object.create(new Storage());
Хороший! Это выглядит хорошо
Пожалуйста, предоставьте код функций
isTokenAdmin
,isTokenActive
.