【问题标题】:How to unit test apollo links如何对阿波罗链接进行单元测试
【发布时间】:2021-06-08 21:31:06
【问题描述】:

我目前有点卡在如何在我的 react 应用程序中测试一些 apollo 链接,因为官方文档似乎只提供了有关如何在组件连接到提供程序后对其进行测试的建议。

目前我有 2 个链接:一个用于注入授权令牌,一个用于在服务器返回响应后刷新它;我想要做的是单独测试它们,或者测试客户端(将在这些链接上构建,以及一个简单的HttpLink)在满足条件时执行它们的逻辑。

以下是它们的实现:

// InjectToken.ts
import { setContext } from '@apollo/client/link/context';

import { getToken } from '../../authentication';

const authenticationLink = setContext(async (_, { headers }) => {

  // Fetches the token from the local storage
  const token = await getToken();

  if (token) {
    return {
      headers: {
        ...headers,
        Authorization: `Bearer ${token}`
      }
    }
  }

  return { headers };
});

export default authenticationLink;
// RefreshToken.ts
import { ApolloLink } from '@apollo/client';

import { refreshToken } from '../../authentication';

const resetTokenLink = new ApolloLink(
  (operation, forward) => forward(operation).map(response => {
    const context = operation.getContext();
    refreshToken(context);
    return response;
  })
);

export default resetTokenLink;

我虽然关于使用 MockProvider 和 apollo 的 useQueryuseMutation 钩子之一通过模拟响应通过客户端触发“假”请求,但似乎这个模拟提供者实际上在解析之前模拟了客户端假数据,所以它不在我的桌子上。

我考虑的第二个选项是关注this guide,它基本上将您的链接与您调用expect 方法的自定义“断言”链接连接起来。

虽然很有希望,但该实现对我来说并不能开箱即用,因为测试没有等待 execute 调用完成(没有断言成功执行),所以我做了一些更改以将其包装在一个承诺中像这样:

// mockAssertForLink.ts

// This performs the mock request
async function mockExecuteRequest(link: ApolloLink): Promise<void> {
  return new Promise<void>((resolve): void => {
    const lastLink = new ApolloLink(() => {
      resolve();
      return null;
    })
    execute(ApolloLink.from([link, lastLink]), { query: MockQuery}).subscribe((): void => {
      // Not required for our tests, subscribe merely fires the request
    });
  })
}

// This exposes the assertionCallback after the promise fulfills, and reports the operation object.
export default async function mockAssertForLink(
  link: ApolloLink,
  assertionCallback: (operation: Operation) => void
): Promise<void> {
  return mockExecuteRequest(ApolloLink.from([
    link,
    new ApolloLink((operation, forward) => {
      assertionCallback(operation);
      return forward(operation);
    })
  ]))
}

通过这个实现,我基本上为每个要对其执行测试的链接创建了两个额外的链接:

  • 一个公开断言回调,我可以在其中检查操作的上下文
  • 一个实际调用 Promise.resolve() 的函数,这将防止我的异步测试在执行时卡住

我的测试是这样使用mockAssertForLink

// InjectToken.test.ts
it('correctly injects authorization header', async () => {
  mocked(getToken).mockResolvedValue(mockToken);
  await mockAssertForLink(authenticationLink, operation => {
    expect(operation.getContext().headers.Authorization).toBe(`Bearer ${mockToken}`)
  });
});

// RefreshToken.ts
it('correctly refreshes the token', async () => {
  await mockAssertForLink(resetTokenLink, () => {
    expect(refreshToken).toHaveBeenCalledTimes(1);
  });
});

这适用于第一个链接,我只是在其中注入一个标头,但在第二个链接上,断言总是失败,仔细观察后,似乎我在 map 方法中定义的内容从未被调用过。

现在,我不确定这是否是进行此类测试的正确方法,因为有关该主题的文档有点缺乏。我想知道的是:

  • 这种测试 apollo 的方法实际上是否可行,还是有更好的方法来测试我的客户的配置?
  • 如果我继续使用这种方法,有没有办法可以强制在链接上调用 map 方法?

任何帮助将不胜感激。

【问题讨论】:

    标签: reactjs typescript unit-testing mocking apollo-client


    【解决方案1】:

    我有同样的一般性问题,并决定在链接执行工具中的适当时间解决承诺。

    您没有看到 map 被呼叫有几个原因:

    • map 在结果 observable 上被调用,并且您没有从终止链接返回结果,因此 map 永远不会在您的测试链接上被调用。
    • 如果您确实在终止链接中返回了结果,那么您执行断言的当前位置是之前 map 可以被调用。您需要将断言延迟到响应处理逻辑运行之后。

    可以将您的工具简化为添加一个终止链接,然后将您的承诺解决逻辑移动到订阅调用中。请参见下面的示例:

    import { ApolloLink, execute, FetchResult, gql, GraphQLRequest, Observable, Operation } from '@apollo/client';
    
    const MockQuery = gql`
      query {
        thing
      }
    `;
    
    interface LinkResult<T> {
      operation: Operation;
      result: FetchResult<T>;
    }
    
    async function executeLink<T = any, U = any>(
      linkToTest: ApolloLink,
      request: GraphQLRequest = { query: MockQuery },
      responseToReturn: FetchResult<U> = { data: null }
    ) {
      const linkResult = {} as LinkResult<T>;
    
      return new Promise<LinkResult<T>>((resolve, reject) => {
        const terminatingLink = new ApolloLink((operation) => {
          linkResult.operation = operation;
          return Observable.of(responseToReturn);
        });
    
        execute(ApolloLink.from([linkToTest, terminatingLink]), request).subscribe(
          (result) => {
            linkResult.result = result as FetchResult<T>;
          },
          (error) => {
            reject(error);
          },
          () => {
            resolve(linkResult);
          }
        );
      });
    }
    
    it('calls refreshToken', async () => {
      const refreshToken = jest.fn();
      const resetTokenLink = new ApolloLink((operation, forward) => {
        operation.variables.test = 'hi';
    
        return forward(operation)
          .map((response) => {
            refreshToken(operation.getContext());
            return response;
          })
          .map((response) => {
            (response.context ??= {}).addedSomething = true;
            return response;
          });
      });
    
      const { operation, result } = await executeLink(resetTokenLink);
    
      expect(refreshToken).toHaveBeenCalled();
      expect(operation.variables.test).toBe('hi');
      expect(result.context?.addedSomething).toBe(true);
    });
    

    我没有在链接中插入断言逻辑来运行,而是在 Promise 中捕获操作和结果值。您当然可以创建其他自定义链接以在链接链中的特定点插入断言,但在最后断言结果似乎更好。

    【讨论】:

      猜你喜欢
      • 2019-10-20
      • 2019-08-13
      • 2020-06-12
      • 2018-12-06
      • 2018-05-27
      • 2013-02-02
      • 2021-12-28
      • 2017-10-15
      • 2011-03-08
      相关资源
      最近更新 更多