Магия декларативности и схемы. Часть 5: Тесты
Напоминалка про пример
Вы уже добрались до пятого поста и наверняка помните, что мы пишем wiki для любителей функционального программирования. Каждая страничка посвящена алгебраическому типу.
У нас уже есть готовый запрос
и схема
Все уже достаточно надежно?
У нас уже есть линтеры, все запросы валидируются по схеме, а TypeScript пристально следит за тем, что мы используем данные GraphQL-ответов правильно. Можем ли мы сделать наше приложение еще более надежным? Разумеется. Настало время написать тесты.
Чуть-чуть холивара про Apollo, архитектуру и тесты
Думать об архитектуре – штука полезная. Многие фреймворки, такие как apollo
предоставляют вам возможность
использовать GraphQL-запросы прямо внутри ваших компонентов. Кроме этого apollo
заботится обо всех нюансах взаимодействия с GraphQL engine (где бы он ни находился): делает запросы, денормализует ответы и заботливо складывает все в стор (cache в терминах apollo).
Типичный случай использования apollo: вы закидываете хуки в самое удобное место в вашем компоненте и прямо внутри компонента работаете с данными, которые вам выдал GraphQL движок.
У такого подхода есть очевидный плюс – думать не надо :) Более того, если у вас не очень сложная стуктура файлов, никаких хитрых процессов работы с данными, полученными от GraphQL engine не планируется, то изобретать велосипед не имеет смысла. Вот только если вы будете недостаточно аккуратны, логика вашего представления смешается с логикой работы с GraphQL-движком. И вскоре с поддержкой вашего кода все будет плохо.
Например: К вам приходит грустный бэкенд и сообщает, что его сервер мучают большим количеством однотипных GraphQL запросов. Вы идете разбираться и находите десяток компонентов, которые используют различные вариации этого запроса. Да еще пробрасывают ответ дальше по дереву. Оптимизировать что-то в подобной ситуации будет не просто.
Решение тут достаточно простое: вытащите логику взаимодействия с вашим GraphQL запросом на отдельный слой.
Для этого вам совсем не обязательно заводить папки со странными названиями в строгом соответствии рекомендациями дядюшки Боба. Просто следите за структурой ваших компонентов и старайтесь, чтобы логику взаимодействия с вашим запросом было легко обнаружить и починить.
Вы можете пойти дальше и запретить выполнять GraphQL-запросы из ваших компонентов. Сложить все связанное с GraphQL в отдельное место и прикрутить к этому ~~педали~~ интерфейс.
Что бы вы ни выбрали, если хотите получить действительно надежную систему, вам придется написать для нее тесты. Если ваш фреймворк берет на себя все взаимодействие с GraphQL, то тестировать это взаимодействие не нужно.
В примере с Apollo вот такой тест будет лишним
const testQuery = gql`
query TestPages {
allPages {
id
text
}
}
`
test('it works!', async () => {
const response = await client.query({ query: testQuery })
expect(response.data.allPages).toBeDefined()
})
Однако если вы:
- Тестируете логику процессинга ваших GraphQL данных,
- Тестируете работу компонента, который использует GraphQL,
- Тестируете хитрый хук который как-то использует GraphQL,
напишите для этого тесты :)
Как написать тесты, если в лапках держим схему?
Вам понадобится сущность, которая инкапсулирует логику работы в вашим GraphQL движком. Если у вас такой нет, подумайте, все ли окей с архитектурой вашего приложения :)
До тех пор, пока вы не начали писать интеграционные тесты, забудьте, что у вас есть настоящий GraphQL сервер. Замокайте эту сущность или попросите ваш фреймворк сделать это за вас. Мы уже научились мокать наш GraphQL engine, помните?
Используйте замоканное в тестах.
apollo client case
Если используется @apollo/client
и react — все уже сделано за нас. Нужно просто воспользоваться MockedProvider
В примерах тестов я также буду использовать @testing-library
В качестве мока мы закидываем запрос и ожидаемый ответ. Дальше первым expect
проверяем состояние загрузки, вторым – что загруженные данные успешно отрендерились.
Если данные нас не очень волнуют или мы хотим только частично поменять ответ, можно воспользоваться автомоками. Я рассказывала о них во второй части этой серии постов.
Чтобы не повторяться, мы написали два хелпера getSchemaWithMocks
и createMock
. Мы можем продолжить наш рефакторинг дальше, например, добавив наши моки прямо в объект MockedProvider
.
Я здесь всего лишь хотела показать идею, так что дальнейший рефакторинг предоставлю заинтересованным читателям :)
А как настроить Jest и testing library?
Ответ на этот вопрос лежит за скоупом этой статьи. В документации все подробненько расписано. Единственная трудность, на которую я хочу обратить ваше внимание — это то, как заставить Jest понимать .graphql файлики. Вам понадобится специальный трансформер. Тут возможны два сценария:
- Если вы грузите grapqhl raw-loaderом, как я в этих постах, можете написать свой.
// raw-transformer.js
module.exports = {
process(sourceText) {
return {
code: `module.exports = ${JSON.stringify(sourceText)}`,
}
},
}
и добавить его в конфиг Jest:
// jest.config.js
module.exports = {
// ...
transform: {
'.(graphql|gql)$': '<rootDir>/raw-transformer.js',
},
}
- Вы используете
grapqhl-tag/loader
Тогда просто воспользуйтесь готовым транcформеромjest-transform-graphql
Осталось чуть-чуть
Теперь мы понимаем, как сделать наше приложение еще более надежным, добавив тесты. В следующем (и, я надеюсь, завершающем 😅) посте я расскажу, как защищаться от злобных бэкендов, удаляющих ваши любимые поля из графа.