Как использовать Jest для тестирования компонента реакции с помощью localStorage?

У меня есть компонент, который обращается к локальному хранилищу, и я хочу протестировать его с помощью 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;
};

Пожалуйста, предоставьте код функций isTokenAdmin, isTokenActive.

Lin Du 14.12.2020 05:33

Это потому, что вы пытаетесь получить доступ к localStorage непосредственно из браузера как к глобальной переменной. Вам нужно либо объявить его в своих глобальных переменных, либо издеваться над ним (некоторые также используют jsdom, чтобы издеваться над window объектом)

Simon Ifergan 14.12.2020 08:46

Только что отредактировал вопрос, чтобы включить код для isTokenAdmin, isTokenActive

Ry10p 14.12.2020 18:36
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Навигация по приложениям React: Исчерпывающее руководство по React Router
Навигация по приложениям React: Исчерпывающее руководство по React Router
React Router стала незаменимой библиотекой для создания одностраничных приложений с навигацией в React. В этой статье блога мы подробно рассмотрим...
Массив зависимостей в React
Массив зависимостей в React
Все о массиве Dependency и его связи с useEffect.
5
3
14 431
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Вы правы, что 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());

Хороший! Это выглядит хорошо

Jonathan Irwin 15.03.2023 17:20

Другие вопросы по теме