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