阿波罗的 MockedProvider 没有明确警告缺少模拟

apollo's MockedProvider doesn't warn clearly about missing mocks

我正在使用 apollo 挂钩(useQueryuseMutation)对 React 组件进行单元测试,并且在测试中我使用 apollo 的 MockedProvider 模拟实际查询。问题是有时,我的模拟与组件实际进行的查询不匹配(创建模拟时的拼写错误,或者组件演变并更改了一些查询变量)。发生这种情况时,MockedProvided returns 组件将出现 NetworkError。但是在测试套件中,没有显示警告。这令人沮丧,因为有时我的组件对 useQuery 返回的错误不做任何处理。这导致我曾经通过的测试突然无声地失败了,让我很难找到原因。

这是使用 useQuery 的组件示例:

import React from 'react';
import {gql} from 'apollo-boost';
import {useQuery} from '@apollo/react-hooks';


export const gqlArticle = gql`
  query Article($id: ID){
    article(id: $id){
      title
      content
    }
  }
`;


export function MyArticleComponent(props) {

  const {data} = useQuery(gqlArticle, {
    variables: {
      id: 5
    }
  });

  if (data) {
    return (
      <div className="article">
        <h1>{data.article.title}</h1>
        <p>{data.article.content}</p>
      </div>
    );
  } else {
    return null;
  }
}

这是一个单元测试,我在其中犯了一个错误,因为 mock 的变量对象是 {id: 6} 而不是组件将请求的 {id: 5}

  it('the missing mock fails silently, which makes it hard to debug', async () => {
    let gqlMocks = [{
      request:{
        query: gqlArticle,
        variables: {
          /* Here, the component calls with {"id": 5}, so the mock won't work */
          "id": 6,
        }
      },
      result: {
        "data": {
          "article": {
            "title": "This is an article",
            "content": "It talks about many things",
            "__typename": "Article"
          }
        }
      }
    }];

    const {container, findByText} = render(
      <MockedProvider mocks={gqlMocks}>
        <MyArticleComponent />
      </MockedProvider>
    );

    /* 
     * The test will fail here, because the mock doesn't match the request made by MyArticleComponent, which
     * in turns renders nothing. However, no explicit warning or error is displayed by default on the console,
     * which makes it hard to debug
     */
    let titleElement = await findByText("This is an article");
    expect(titleElement).toBeDefined();
  });

如何在控制台中显示明确的警告?

我已经向 apollo 团队提交了一份 Github issue,以建议一种内置的方法来执行此操作。同时,这是我自制的解决方案。

想法是给 MockedProvider 一个自定义的阿波罗 link。默认情况下,它使用 MockLink 用给定的模拟初始化。取而代之的是,我创建了一个自定义 link,这是一个由 MockLink 组成的链,我创建它的方式与 MockedProvider 相同,然后是一个 apollo 错误 link,它会拦截请求可能返回的错误,并将其记录在控制台中。为此,我创建了一个自定义提供程序 MyMockedProvider.

MyMockedProvider.js

import React from 'react';
import {MockedProvider} from '@apollo/react-testing';
import {MockLink} from '@apollo/react-testing';
import {onError} from "apollo-link-error";
import {ApolloLink} from 'apollo-link';

export function MyMockedProvider(props) {
  let {mocks, ...otherProps} = props;

  let mockLink = new MockLink(mocks);
  let errorLoggingLink = onError(({ graphQLErrors, networkError }) => {
    if (graphQLErrors)
      graphQLErrors.map(({ message, locations, path }) =>
        console.log(
          `[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`,
        ),
      );

    if (networkError) console.log(`[Network error]: ${networkError}`);
  });
  let link = ApolloLink.from([errorLoggingLink, mockLink]);

  return <MockedProvider {...otherProps} link={link} />;
}

MyArticleComponent.test.js

import React from 'react';
import {render, cleanup} from '@testing-library/react';

import {MyMockedProvider} from './MyMockedProvider';
import {MyArticleComponent, gqlArticle} from './MyArticleComponent';

afterEach(cleanup);

it('logs MockedProvider warning about the missing mock to the console', async () => {
  let gqlMocks = [{
    request:{
      query: gqlArticle,
      variables: {
        /* Here, the component calls with {"id": 5}, so the mock won't work */
        "id": 6,
      }
    },
    result: {
      "data": {
        "article": {
          "title": "This is an article",
          "content": "It talks about many things",
          "__typename": "Article"
        }
      }
    }
  }];

  let consoleLogSpy = jest.spyOn(console, 'log');

  const {container, findByText} = render(
    <MyMockedProvider mocks={gqlMocks}>
      <MyArticleComponent />
    </MyMockedProvider>
  );

  let expectedConsoleLog = '[Network error]: Error: No more mocked responses for the query: query Article($id: ID) {\n' +
    '  article(id: $id) {\n' +
    '    title\n' +
    '    content\n' +
    '    __typename\n' +
    '  }\n' +
    '}\n' +
    ', variables: {"id":5}';


  await findByText('{"loading":false}');
  expect(consoleLogSpy.mock.calls[0][0]).toEqual(expectedConsoleLog);
});

当 Apollo 的 MockedProvider 没有 return 预期响应时,可能很难调试出错的地方。以下是我偶然发现的一些提示:

1。使用自定义 MockedProvider

这确保全局记录所有错误。如果您的测试预期会出现一些错误,则可能会很嘈杂。

2。记录或处理特定组件中的错误

如果不使用自定义 MockedProvider,如果您处理从查询或突变返回的 error,您仍然可以收到缺少模拟的警报:

const { data, error } = useQuery(myQuery);
console.log(error); // 'no more mocked responses'

3。在您的模拟中添加日志记录以查看它是否被调用

您的模拟 return 是一个 result 对象文字。 result 也可以是一个函数,它只在你的 mock 被调用时执行。您可以从此记录或添加其他工具来验证查询或突变是否已执行。

4。确保变量完全匹配

仅当调用查询或变异的变量与模拟完全匹配时,才会调用您的模拟。

5。当一切都失败时

有时上述所有操作都很好,但您仍然无法从查询或变更中获取数据。这几乎可以肯定是因为您的 mock returns 的数据与预期格式不完全匹配。 这将无提示地失败。除了验证您的数据格式外,我还不知道如何解决此问题。

我什至遇到过这样一种情况,我的带有 Apollo 生成类型的 TypeScript 代码正在编译,但我没有收到回复,因为我的一个类型有点不对劲(也许我发送了额外的字段,我不记得了)。