8/7/2022

Магия декларативности и схемы. Часть 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()
})

Однако если вы:

  1. Тестируете логику процессинга ваших GraphQL данных,
  2. Тестируете работу компонента, который использует GraphQL,
  3. Тестируете хитрый хук который как-то использует GraphQL,

напишите для этого тесты :)

Как написать тесты, если в лапках держим схему?

Вам понадобится сущность, которая инкапсулирует логику работы в вашим GraphQL движком. Если у вас такой нет, подумайте, все ли окей с архитектурой вашего приложения :)

До тех пор, пока вы не начали писать интеграционные тесты, забудьте, что у вас есть настоящий GraphQL сервер. Замокайте эту сущность или попросите ваш фреймворк сделать это за вас. Мы уже научились мокать наш GraphQL engine, помните?

Используйте замоканное в тестах.

apollo client case

Если используется @apollo/client и react — все уже сделано за нас. Нужно просто воспользоваться MockedProvider В примерах тестов я также буду использовать @testing-library


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

Если данные нас не очень волнуют или мы хотим только частично поменять ответ, можно воспользоваться автомоками. Я рассказывала о них во второй части этой серии постов.


Чтобы не повторяться, мы написали два хелпера getSchemaWithMocks и createMock. Мы можем продолжить наш рефакторинг дальше, например, добавив наши моки прямо в объект MockedProvider. Я здесь всего лишь хотела показать идею, так что дальнейший рефакторинг предоставлю заинтересованным читателям :)

А как настроить Jest и testing library?

Ответ на этот вопрос лежит за скоупом этой статьи. В документации все подробненько расписано. Единственная трудность, на которую я хочу обратить ваше внимание — это то, как заставить Jest понимать .graphql файлики. Вам понадобится специальный трансформер. Тут возможны два сценария:

  1. Если вы грузите 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',
  },
}
  1. Вы используете grapqhl-tag/loader Тогда просто воспользуйтесь готовым транcформером jest-transform-graphql

Осталось чуть-чуть

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