DevGang
Авторизоваться

Создание и тестирование клиентских ссылок Apollo

При использовании клиента Apollo может наступить момент, когда вы захотите проверить или внести некоторые изменения в свои запросы GraphQL. Именно здесь в игру вступает Apollo Link. Библиотека устанавливает цепочку действий для каждой операции GraphQL, выполняемой клиентом.

Что такое ссылка и как ее создать и протестировать? Давайте разберемся и посмотрим, что происходит.

Понимание концепции ссылки

Чтобы понять концепцию ссылки в контексте клиента Apollo, давайте начнем с того, что нам говорят документы:

Библиотека Apollo Link помогает вам настроить поток данных между клиентом Apollo и вашим сервером GraphQL. Вы можете определить сетевое поведение вашего клиента как цепочку объектов ссылок, которые выполняются последовательно. Каждая ссылка должна представлять собой либо самостоятельную модификацию операции GraphQL, либо побочный эффект (например, ведение журнала). Источник

Когда клиент Apollo инициирует запрос, запрос проходит по цепочке ссылок, пока не достигнет завершающей ссылки. Обычно завершающая ссылка отвечает за фактическое получение данных на сервер GraphQL и управление последующим ответом. Затем ответ возвращается по цепочке в обратном порядке.

// The `from` function combines an array of individual links into a link chain
const client = new ApolloClient({
  link: ApolloLink.from([errorLink, myCustomLink, httpLink]),
  ...
});

// alternatively, you can also use concat like so
const client = new ApolloClient({
  link: errorLink.concat(httpLink)
  ...
});

Создание ссылки

Существует два типа ссылок: без сохранения состояния и с сохранением состояния. Ссылка является экземпляром класса ApolloLink (или его подкласса), и каждая ссылка требует определения обработчика запроса: request(operation, forward).

Давайте кратко рассмотрим эти два параметра обработчика запроса:

  • operation — это вся информация о текущем, operation — весь запрос и то, что к нему прилагается. Здесь мы можем легко получить доступ operation.query и operation.variables понять информацию о запросе, а также остальную часть context запроса через operation.getContext()
  • forward — это функция, которая принимает операцию и пересылает ее по следующей ссылке, вот так: forward(operation)

Обработка ответов в цепочке ссылок

Теперь, когда мы успешно переправили операцию по цепочке ссылок, пришло время обработать ответ на обратном пути по цепочке. Для этого нам нужно установить наблюдателя и подписаться на ответ.

К счастью, этот процесс довольно прост. Клиент Apollo предоставляет класс Observable именно для этой цели. Вы можете использовать этот класс для настройки подписки наблюдателя. Вот как это делается:

import { Observable } from '@apollo/client';

// the required request handler
request(operation, forward) {
  return new Observable((observer) => {
    const subscription = forward(operation).subscribe({
      next: (result) => { 
        observer.next(result);
        observer.complete();
      },
      error: (error) => observer.error(error),
      complete: () => observer.complete(),
    });

    return () => {
      subscription.unsubscribe();
    }
  });
}

В приведенном выше фрагменте кода мы создаем новый экземпляр Observable внутри функции запроса. Этот экземпляр Observable облегчает подписку на ответ. Мы подписываемся на ответ, пересылая операцию и определяя обработчики: next, который получает данные об успешной попытке операции, error и complete, которые сигнализируют, что мы завершили обработку ответа.

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

request(operation, forward) {
  // set the startTime before we forward the operation
  operation.setContext({
    startTime: Date.now(),
  });

  return new Observable((observer) => {
    const subscription = forward(operation).subscribe({
      next: (result) => { 
        const { operationName } = operation;
        const startTime = operation.getContext().startTime;

        if (operationName && startTime) {
          // calculate total request time in our observer
          logRequestTime(
            operationName, 
            Date.now() - startTime
          );
        }

        observer.next(result);         
        observer.complete();
      },
      error: (error) => observer.error(error),
      complete: () => observer.complete(),
    });

    return () => {
      subscription.unsubscribe();
    }
  });
}

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

Тестирование ссылок Apollo

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

Вот пример макета:

const mockLink = new ApolloLink((operation) => {
  const context = operation.getContext();
  const { mockError, mockResponse } = context || {};

  return new Observable((observer) => {
    if (mockError) {
      observer.error(mockError);
      return;
    }

    if (mockResponse) {
      observer.next({ data: mockResponse });
      observer.complete();
      return;
    }

    observer.error(
      new Error('mockLink: You must provide mockError or mockResponse')
    );
  });
});

В функции mockLink мы проверяем контекст операции на наличие mockError или mockResponse. Мы создаем Observable, который, в зависимости от контекста, выдает либо ложную ошибку, либо ложный ответ, завернутый в ожидаемую структуру данных. В случае, если ни то, ни другое не указано, то mockLink генерирует ошибку, чтобы гарантировать, что тестовые примеры явно определяют ожидаемое поведение.

Затем эта ссылка отправляет данные или ошибку обратно по цепочке (в нашем случае обратно на ссылку, которую мы тестируем).

Простая ссылка на тест

Давайте посмотрим на это в действии, чтобы лучше понять это. Представьте, что наша ссылка OperationHistoryLink делает именно это: сохраняет порядок наших операций по мере их выполнения. Мы можем создать ссылку с сохранением состояния, чтобы справиться с этим за нас:

import { ApolloLink } from '@apollo/client';

// stateful link
class OperationHistoryLink extends ApolloLink {
  operationHistory = [];

  request(operation, forward) {
    this.operationHistory.push(operation);
    return forward(operation);
  }
}

Чтобы проверить это, мы должны выполнить запрос, а затем подтвердить, что операция добавляется в массив в случае ее успеха.

import { execute } from '@apollo/client';

describe('OperationHistoryLink', () => {
  it('adds operation to the operationHistory array on success', () => {
    const operationHistoryLink = new OperationHistoryLink();
    const composedLink = operationHistoryLink.concat(mockLink);

    return new Promise((resolve) => {
      const operation = {
        query: 'some query',
        context: {
          mockResponse: { foo: 'bar' },
        },
      };

      execute(composedLink, operation).subscribe({
        next: ({ data }) => {
          expect(data.foo).toBe('bar'); // mockResponse is handled by mockLink
          expect(operationHistoryLink.operationHistory).toHaveLength(1);
          expect(operationHistoryLink.operationHistory[0].query).toEqual(
            operation.query,
          );
          resolve();
        },
        error: () => {
          // optionally reject or throw here, we shouldn't get an error
        },
        complete: () => {},
      });
    });
  });
});

Несколько замечаний по поводу нашего теста:

  • Чтобы запустить нашу операцию GraphQL, мы используем execute функцию, предоставляемую клиентом Apollo. Эта функция начинает процесс отправки операции по цепочке ссылок, создавая подписку наблюдателя с той же подписью, которую мы использовали в самой нашей ссылке ( nexterrorcomplete)
  • Чтобы гарантировать, что наш тест успеет выполниться, мы используем файл Promise. Это позволяет нам дождаться разрешения промиса, гарантируя, что наши тестовые утверждения будут выполнены только после того, как операция GraphQL завершит свое выполнение.
  • Наши утверждения находятся внутри next обратного вызова подписки. Конечно, когда наши данные возвращаются из mockLink, мы можем ожидать, что они будут соответствовать тому, что мы отправили в контексте. Однако главное, что мы здесь утверждаем, это то, что длина operationHistory нашей OperationHistoryLink ссылки равна 1. Если это правда, мы знаем, что наша ссылка работает так, как задумано!
OperationHistoryLink
    ✓ adds operation to the operationHistory array on success (3 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        2.141 s

Успех! Теперь мы успешно создали пользовательскую ссылку и написали тест для проверки ее функциональности.

Хотите попробовать сами? Вы можете просмотреть весь код здесь

Источник:

#JavaScript #GraphQL
Комментарии
Чтобы оставить комментарий, необходимо авторизоваться