Это продолжение истории, которая началась здесь, а продолжалась здесь и здесь.
В прошлой части я написал интеграционный тест, демонстрирующий процесс инициализации и выполнения полного набора обработчиков, извлекающих данные из базы. Но поскольку от написания этого теста, до его запуска, может пройти слишком длительное время, необходимое для кодирования не только обработчика, но и правил настройки для всех необходимых запросов к базе, то сегодня я решил реализовать его модульную версию, расчитанную на конфигурирование и запуск всего одного обработчика. Выглядит это тест вот как:
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(); } });
Запускаю тесты и получаю сообщение о том, что модуль с правилами не найден. Еще раз, что нужно будет обработчику извлекать из правил?
- Наименование ключа свойства контейнера состояния, к которому будет привязываться полученная из базы данных запись
- Наименование таблицы базы данных, из которой нужно будет извлечь запись
- Метод, формирующий запрос, который нужно будет послать базе данных, чтобы получить ответ
- Метод-диспетчер, отправляющий полученную из базы запись хранилищу контейнера состояния.
- Метод-диспетчер, отправляющий ошибку, если таковая произойдет, хранилищу контейнера состояния. Я решил сначала консолидировать ошибки в контейнере состояния, а потом уже разбираться с ними.
Набор правил представляется мне словарем (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(); }
Запускаю тесты еще раз. Все работает так, как ожидается. Можно считать что правильный прототип команды запроса к базе готов и возвращаться к рефакторингу и разработке правил конфигурирования запросов ибо уже сейчас видно что код генерации набора правил перестанет быть читабельным после добавления буквально еще пары правил в таком же формате.
