阿波罗的 MockedProvider 没有明确警告丢失的模拟

Posted

技术标签:

【中文标题】阿波罗的 MockedProvider 没有明确警告丢失的模拟【英文标题】:apollo's MockedProvider doesn't warn clearly about missing mocks 【发布时间】:2020-05-22 19:12:04 【问题描述】:

我正在使用 apollo 钩子(useQueryuseMutation)对 React 组件进行单元测试,并在测试中使用 apollo 的 MockedProvider 模拟实际查询。问题是有时,我的模拟与组件实际发出的查询不匹配(创建模拟时的拼写错误,或者组件演变并更改了一些查询变量)。发生这种情况时,MockedProvided 会向组件返回 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;
  

这是一个单元测试,我犯了一个错误,因为模拟的变量对象是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();
  );

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

【问题讨论】:

我只是想感谢您提出这个问题并详细解释问题。我刚刚遇到了同样的问题,感谢上帝在这里找到了你的帖子!现在我的测试开始了! 【参考方案1】:

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

这个想法是给MockedProvider一个自定义的阿波罗链接。默认情况下,它使用MockLink 用给定的模拟初始化。取而代之的是,我创建了一个自定义链接,它是一个由MockLink 形成的链,我以与MockedProvider 相同的方式创建它,然后是一个阿波罗错误链接,它拦截可能由请求返回的错误,并将它们记录在控制台中。为此,我创建了一个自定义提供程序 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);
);

【讨论】:

并非所有英雄都穿斗篷 先生,您是救命稻草【参考方案2】:

当 Apollo 的 MockedProvider 没有返回预期的响应时,可能很难调试出了什么问题。以下是我偶然发现的一些提示:

1。使用自定义MockedProvider

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

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

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

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

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

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

4。确保变量完全匹配

只有在调用查询或突变的变量与模拟完全匹配时才会调用模拟。

5。当一切都失败时

有时上述所有情况都很好,但您仍然无法从查询或突变中获取数据。这几乎可以肯定是因为您的模拟返回的数据与预期格式不完全匹配。 这将静默失败。除了验证您的数据格式之外,我还不知道如何解决此问题。

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

【讨论】:

以上是关于阿波罗的 MockedProvider 没有明确警告丢失的模拟的主要内容,如果未能解决你的问题,请参考以下文章

apollo MockedProvider + Storybook 不工作

使用 MockedProvider 进行测试时使用 Apollo 突变更新存储

如何模拟 useApolloClient 钩子?

使用酶进行测试时如何找到包裹在 MockedProvider 中的 Typography 组件

reactjs / jest:无法使用 MockedProvider 用数据填充 react-apollo 组件?

京东多端全流程交易解决方案阿波罗平台iOS单元测试实践