Разработка команды запроса данных из базы — часть 4, завершающая

habr.png

Это продолжение истории, которая началась здесь, а продолжалась здесь и здесь.

В прошлой части я написал интеграционный тест, демонстрирующий процесс инициализации и выполнения полного набора обработчиков, извлекающих данные из базы. Но поскольку от написания этого теста, до его запуска, может пройти слишком длительное время, необходимое для кодирования не только обработчика, но и правил настройки для всех необходимых запросов к базе, то сегодня я решил реализовать его модульную версию, расчитанную на конфигурирование и запуск всего одного обработчика. Выглядит это тест вот как:

describe('requestHandler', () => {

  const createStore = require('redux').createStore;
  const reducers = require('../../src/reducers.js');
  const DbMock = require('../mocks/DbMock');
  const db = new DbMock();
  const rules = require('../../src/rules');
  const dbRequest = require('../../src/storage/dbRequest');
  let request = null,
      store = null,
      context = null;

  beforeEach(() => {
    store = createStore(reducers);
    context = {
      db,
      store,
      rules
    };
    request = dbRequest.bind(context, [ 'user' ]);

    expect(store.getState().user).toBeNull();
    expect(store.getState().error).toEqual([]);
  });

  it('should get user from database', (done) => {

    const assert = checkUser.bind(context, [ done ]);
    store.subscribe(assert);

    store.dispatch({type: 'NOTE', note: { Id: 1, UserRecordId: 1 }});
    request();
  });

  function checkUser(args) {

    const state = store.getState();

    if(state.user === null)
      return;

    const user = state.user;

    expect(user.Id).toEqual(1);
    expect(user.Name).toEqual('Jack');

    const checkIsCompleted = args[0];
    checkIsCompleted();
  }
});

Запускаю тесты и получаю сообщение о том, что модуль с правилами не найден. Еще раз, что нужно будет обработчику извлекать из правил?


  1. Наименование ключа свойства контейнера состояния, к которому будет привязываться полученная из базы данных запись
  2. Наименование таблицы базы данных, из которой нужно будет извлечь запись
  3. Метод, формирующий запрос, который нужно будет послать базе данных, чтобы получить ответ
  4. Метод-диспетчер, отправляющий полученную из базы запись хранилищу контейнера состояния.
  5. Метод-диспетчер, отправляющий ошибку, если таковая произойдет, хранилищу контейнера состояния. Я решил сначала консолидировать ошибки в контейнере состояния, а потом уже разбираться с ними.

Набор правил представляется мне словарем (Map), в котором ключом будет имя свойства контейнера состояния и я пожалуй сформулирую первый модульный тест:

describe('rules', () => {

  const rules = require('../src/rules');

  it('should contain user rules', () => {

    const rule = rules.get('user');

    expect(rule.table).toEqual('users');
  });
});

Запускаю тесты и Jasmine сообщает мне что теперь у меня два невыполняющихся теста. Чтобы не усложнять задачу, начну с простейшего правила, которое сообщает мне что для присвоения значения ключу user контейнера состояния, моему запросу следует обращаться за данными в таблицу users. Вроде бы все логично. Напишу как мне кажется немного кода.


const makeRules = () => {

  const rules = new Map();

  rules.set('user', { table: 'users' });

  return rules;
};

module.exports = makeRules();

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

Немного доработаю тест словаря правил. Добавлю код, который проверяет наличие метода-диспетчера, обрабатывающего ошибку выполнения запроса к базе:

describe('rules', () => {

  const rules = require('../src/rules');

  it('should contain user rules', () => {

    const rule = rules.get('user');

    expect(rule.table).toEqual('users');

    expect(rule.onError.name).toEqual('dispatchError');
    expect(typeof rule.onError).toEqual('function');
  });
});

Выполняю тесты, убеждаюсь что у меня снова два неисправных теста, и возвращаюсь к доработке кода фабричного метода для генерации словаря правил. Добавляю в литерал объекта первого правила функцию:


const makeRules = () => {

  const rules = new Map();

  rules.set('user', { 
      table: 'users',
      onError: function dispatchError(error, store) {
        const action = { type: 'ERROR', error };
        store.dispatch(action);        
      }
  });

  return rules;
};

module.exports = makeRules();

Снова запускаю тесты. Новый фрагмент правила успешно проходит тест, так что я решаю добавить проверки для всех оставшихся правил:

describe('rules', () => {

  const rules = require('../src/rules');

  it('should contain user rules', () => {

    const rule = rules.get('user');

    expect(rule.table).toEqual('users');

    expect(rule.onError.name).toEqual('dispatchError');
    expect(typeof rule.onError).toEqual('function');

    expect(rule.onSuccess.name).toEqual('dispatchUser');
    expect(typeof rule.onSuccess).toEqual('function');

    expect(rule.query.name).toEqual('getUserQuery');
    expect(typeof rule.query).toEqual('function');
  });
});

Запускаю тесты. Тестовый набор для словаря правил опять выдает ошибку. Пишу код:


const makeRules = () => {

  const rules = new Map();

  rules.set('user', { 
      table: 'users',
      onError: function dispatchError(error, store) {        
        const action = { type: 'ERROR', error };
        store.dispatch(action);        
      },
      onSuccess: function dispatchUser(user, store) {        
        const action = { type: 'USER', user };
        store.dispatch(action);
      },
      query: function getUserQuery(store) {
        const state = store.getState();

        if(state.note === null)
          return null;

        return { Id: state.note.UserRecordId };
      }
  });

  return rules;
};

module.exports = makeRules();

Запускаю тесты. Тест набора правил опять успешно выполняется и как мне кажется я могу теперь взяться за написание кода для собственно новой версии запроса данных из базы. В этот раз я не буду пользоваться синтаксисом классов, потому что не вижу никаких преимуществ от его использования. Кода будет много сразу, потому что оттестированную его часть я безжалостно скопирую из существующей реализации запроса, дополнив ее проверкой, на случай если запись из базы уже была извлечена и помещена в контейнер состояния. Итак код:

function dbRequest(args){

  const key = args[0];
  const getQuery = this.rules.get(key).query;
  const dispatchUser = this.rules.get(key).onSuccess;
  const dispatchError = this.rules.get(key).onError;
  const tableName = this.rules.get(key).table;
  const table = this.db.Model.extend({
    tableName: tableName
  });
  const state = this.store.getState();

  if(state[key] !== null)
    return;

  const query = getQuery(this.store);

  if(query === null)
    return;

  table.where(query).fetch().then((item) => {

      dispatchUser(item, this.store);
    }).catch((error) => {

      dispatchError(error, this.store);
    });
}
module.exports = dbRequest;

Запускаю тесты… барабанная дробь! И вижу строчку зеленых точек. Все тесты успешно выполнились. И поэтому я добавлю в набор еще один тест, проверяющий корректность обработки ошибок, пока я еще не забыл что чтобы моя псевдобазаданных DbMock вернула ошибку, надо попросить у нее запись с Id равным 555:


  it('should add error in store state', (done) => {

    const assert = checkErrorHasBeenAdded.bind(context, [ done ]);
    store.subscribe(assert);

    store.dispatch({type: 'NOTE', note: { Id: 1, UserRecordId: 555 }});
    request();
  });

  function checkErrorHasBeenAdded(args){

    const state = store.getState();

    if(state.error.length === 0)
      return;

    const error = state.error;

    expect(Array.isArray(error)).toBeTruthy();
    expect(error.length).toEqual(1);
    expect(error[0].message).toEqual('Something goes wrong!');

    const checkIsCompleted = args[0];
    checkIsCompleted();
  }

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

© Habrahabr.ru