Тестирование входа в Redux с помощью fetch api - PullRequest
0 голосов
/ 17 июня 2019

В моем проекте React-Native я пытался запустить модульное тестирование, как тестировать действие Redux, которое включает вызов API api fetch. Я рассмотрел несколько примеров тестирования асинхронного Action, но я не обернулсяя обдумываю, как проверить приведенный ниже код.

Я смотрел на использование redux-mock-store, но не знаю, с чего начать тест.

import {
  API_URL_AUTH,
  API_URL,
  CLIENT_ID,
  CLIENT_SECRET,
} from "../config/consts";

import { FETCHING, FETCHED } from "../actions/ActionTypes";

const { dispatch } = this.props;
const { email, password } = this.state;

const authenticationData = {
  client_id: CLIENT_ID,
  client_secret: CLIENT_SECRET,
  grant_type: "password",
  username: email,
  password: password,
};

dispatch({ type: FETCHING });

let response = await (await fetch(API_URL_AUTH + "oauth/token", {
  headers: {
    Accept: "application/json",
    "Content-Type": "application/json",
  },
  method: "POST",
  body: JSON.stringify(authenticationData),
})).json();

if (!response.error) {
  await AsyncStorage.setItem("accessToken", "Bearer " + response.access_token);
  let user = await (await fetch(API_URL + "user", {
    headers: {
      Accept: "application/json",
      "Content-Type": "application/json",
      Authorization: "Bearer " + response.access_token,
    },
    method: "GET",
  })).json();
} else {
  showMessage({
    message: response.message,
    type: "danger",
  });
}

dispatch({ type: FETCHED });

1 Ответ

0 голосов
/ 17 июня 2019

Intro

В первый раз, когда я активно отвечаю на вопрос о переполнении стека, мне пришлось создать учетную запись, но здесь все идет.

Юнит-тестирование редукционных действий - все о тестировании совместной работы.Всегда разумно отделять «модульные» тесты для совместной работы от простых, простых для всех, модульных тестов

Первая очистка

Сначала я бы абстрагировал вызовы выборки и хранилища локали в их собственные функции-оболочки.Это имеет пару преимуществ:

  • легче тестировать (функции совместной работы Тесты AKA не должны выполнять большую логику, просто вызывая другие функции)
  • API - это ваше управление (легкобиблиотеки подкачки)
// api.js
import {
  API_URL_AUTH,
  API_URL,
  CLIENT_ID,
  CLIENT_SECRET,
} from "../config/consts";

const authenticationData = {
  client_id: CLIENT_ID,
  client_secret: CLIENT_SECRET,
  grant_type: "password",
};

export const fetchToken = ({ username, password }) => {
  return fetch(API_URL_AUTH + "oauth/token", {
    headers: {
      Accept: "application/json",
      "Content-Type": "application/json",
    },
    method: "POST",
    body: JSON.stringify({ ...authenticationData, username, password }),
  })).json()
}

export fetchUser = token => fetch(API_URL + "user", {
  headers: {
    Accept: "application/json",
    "Content-Type": "application/json",
    Authorization: "Bearer " + token,
  },
  method: "GET",
})).json();

// localstorage.js
// import { AsyncStorage } from 'some-library' ???
export const storeItem = (key, value)=> AsyncStorage.setItem(key, value);

Это делает код действий намного более понятным, и это немного более прямолинейно в том, что вы пытаетесь выполнить, что в конечном итоге приведет к более легкому тестированию.

// actions.js
import { fetchToken, fetchUser } from "./api";
import { storeItem } from "./localstorage";
import { FETCHING, FETCHED } from "../actions/ActionTypes";

export const loginUser = ({ username, password }) => async (
  dispatch,
  getState,
  { signInUser, getCurrentUser }
) => {
  dispatch({ type: FETCHING }); // probably needs a better action type

  try {
    const response = await fetchToken({ username, password });

    if (!response.error) {
      await storeItem ("accessToken", "Bearer " + response.access_token);
      await fetchUser(response.access_token)
    } else {
      showMessage({
        message: response.message,
        type: "danger",
      });
    }

    dispatch({ type: FETCHED }); // needs better action type
  } catch (e) {
    showMessage({
      message: response.message,
      type: "danger",
    });
  }
}

Да тесты

Обратите внимание, что (я считаю) действия в Redux являются тестами для коллабораторов, что означает, что они идеально подходят для функций имитации, поскольку вы только тестируете взаимодействия, а не фактическиелогика функций.Простые логические функции просты в модульном тестировании, функции, которые обертывают библиотеки (так что вы можете управлять API), обычно вообще не тестируются, или вы можете написать пару интеграционных тестов, которые проверяют обертки изолированно, но интегрируются ссама библиотека.

Вы видите, что, извлекая логику, сами действия становятся очень прямыми и, как результат, облегчают тестирование.

Из библиотеки testdouble, которую я использовал, следует отметить, что td.when также проверяет вызов.Поэтому, если в вашем коде вы случайно вызвали fetchToken({ email, password }) или что-то еще, тест завершился бы неудачей по двум причинам:

  1. fetchToken был вызван с неверными аргументами
  2. thenResolve hasnне возвращает обещание, содержащее { access_token: 'token123' }, означающее, что fetchUser также не будет вызываться с правильными аргументами, поскольку access_token будет undefined
describe('login', () => {
  let getState;
  let dependencies;
  let dispatch;

  beforeEach(() => {
    // mocking out depencies using testdouble.js (mocking library, dont pay too much attention to it
    getState = td.function('getState');
    dependencies = {
      fetchToken: td.function('fetchToken'),
      fetchUser: td.function('fetchUser'),
      storeItem: td.function('storeItem'),
    };
    dispatch = td.function('dispatch');
  });

  test('shows a message on failure, when the token could not be retrieved', () => {
    // bla
  });

  test('shows a message on failure, when the user could not be retrieved', () => {
    // bla
  });

  test('logs the user in', async () => {
    const credentials = { username: "marco@polo.com", password: '123' };
    const user = { /* some object */ }

    td.when(dependencies.fetchToken(credentials)).thenResolve({
      access_token: 'token123'
    });
    td.when(dependencies.fetchUser('token123')).thenResolve(user);

    await loginUser(credentials)(dispatch, getState, dependencies);

    td.verify(dispatch({ type: FETCHING }));
    td.verify(dispatch({ type: FETCHED }));
  });
});

Заключительные замечания

У меня есть суть на моем github, который является еще одним примером того же принципа, поскольку это общий вопрос для людей, вступающих в конфликт с React.Вы можете найти это здесь: https://gist.github.com/venikx/6e03367300f47625d5373826e86afeae

Я предлагаю вам взглянуть на https://github.com/testdouble/contributing-tests/wiki/Test-Double,, который является удивительным ресурсом для тестирования

И взглянуть на https://www.youtube.com/watch?v=Af4M8GMoxi4, который более подробно объясняет эти типы совместной работы (рассказывает о функциях-оболочках, простых логических функциях и о том, как они должны взаимодействовать с этими функциями совместной работы)

Самая важная часть в ваших тестах - это изоляция поведения, которое не должноне ломается, когда вы реорганизуете часть кода.Тесты должны упростить вам рефакторинг кода, а не сложнее (именно поэтому мы любим функции-оболочки).

Надеюсь, это помогло.Теперь я возвращаюсь к скрытности ^^

...