模拟阿波罗客户端返回 "Response not successful: Received status code 401"

mock-apollo-client returnig "Response not successful: Received status code 401"

我正在编写一个玩具应用程序来了解有关无服务器框架和 AWS AppSync 等的更多信息。 我正在尝试尽可能多地进行 TDD。我正在使用 mock-apollo-client 来模拟 ApolloClient,并且 运行 遇到了问题。当尝试编写测试以确保传递给查询的参数时,测试总是 returns 401 Unauthorized 错误。似乎真正的终点仍在被调用,因为当一个有效的 x-api-key 添加到 ApolloClient 的实例化时,测试 returns 来自 AppSync 服务器的真实值,而不是模拟我期待的价值。我使用的是模拟,而不是间谍,所以我不希望真正的终点被击中。此外,当我添加一个有效的 x-api-key 时,测试失败,因为该函数从未被调用过。

 api › recipes › Given a valid recipe id › Should call query with the id as a param

    expect(jest.fn()).toBeCalledTimes(expected)

    Expected number of calls: 1
    Received number of calls: 0

我预计测试会失败,因为当前没有使用任何参数调用查询,而是因为从未调用模拟函数而失败。

我做错了什么?

代码文件

import { ApolloClient, gql, InMemoryCache } from '@apollo/client';

const client = new ApolloClient({
  uri: 'https://redacted.appsync-api.redacted.amazonaws.com/graphql',
  headers: {
    'x-api-key': 'key-redacted',
  },
  cache: new InMemoryCache(),
});

export const GET_RECIPE_QUERY = gql`
  query {
    getRecipe (title:"Lemon Cheese Cake") {
      title,
      ingredients{
        name,
        amount,
        unit
      },
      steps
    }
  }
`;

const gqlQuery = (title) => {

  return client
    .query({
      query: GET_RECIPE_QUERY,
      variables : { title }
    });
};

export const getRecipe = async (id) => {
  const result = await gqlQuery(id);

  return result.data.getRecipe;
};

测试文件

import { createMockClient } from 'mock-apollo-client';

import { GET_RECIPE_QUERY, getRecipe } from './recipes';

const mockRecipe = {
  title: 'Luke\'s Chocolate Orange',
  ingredients: [
    {
      name: 'Orange',
      amount: 1,
    },
    {
      name: 'Chocolate',
      amount: 250,
      unit: 'grams',
    },
  ],
  steps: [
    'Peel orange',
    'Open chocolate',
    'Eat chocolate',
    'Throw orange away',
  ],
};

const mockClient = createMockClient();
const queryHandler = jest.fn().mockResolvedValue({data: {recipe: mockRecipe}});

mockClient.setRequestHandler(GET_RECIPE_QUERY, queryHandler);

describe('api', () => {
  describe('recipes', () => {
    describe('Given a valid recipe id', () => {
      it('Should call query with the id as a param', async () => {
        const id = 'Luke\'s Chocolate Orange';
        const result = await getRecipe(id);
        
        expect(queryHandler).toBeCalledTimes(1);
        expect(queryHandler).toBeCalledWith(id);
      });
    });
  });
});
Packages Versions
@apollo/client 3.5.10
graphql 16.3.0
@testing-library/jest-dom 5.16.2
@testing-library/react 12.1.4
@testing-library/user-event 13.5.0
jest 27.5.1
mock-apollo-client 1.2.0

mock-apollo-client 始终使用 with ApolloProvider,以便您通过 React context Provider 将模拟 apollo 客户端传递给后代组件。

但是,您的代码无法以这种方式将模拟 apollo 客户端传递给组件。您的代码直接从 Apollo Client 发起请求。我们需要拦截这些 GraphQL 请求。有几种方法可以做到这一点,例如 msw。但是,我将继续使用 mock-apollo-client 库进行演示。

您需要模拟 @apollo/client 模块的 ApolloClient class。我们需要使用 Mocking Partials,我们不想模拟从 @apollo/client 导出的其他东西。由于mock-apollo-client库已经提供了createMockClient函数来创建模拟的apollo客户端,我们不需要自己模拟。

一个工作示例:

recipes.ts:

import { ApolloClient, gql, InMemoryCache } from '@apollo/client';

const client = new ApolloClient({
  uri: 'https://redacted.appsync-api.redacted.amazonaws.com/graphql',
  headers: {
    'x-api-key': 'key-redacted',
  },
  cache: new InMemoryCache(),
});

export const GET_RECIPE_QUERY = gql`
  query {
    getRecipe(title: "Lemon Cheese Cake") {
      title
      ingredients {
        name
        amount
        unit
      }
      steps
    }
  }
`;

const gqlQuery = (title) => {
  return client.query({
    query: GET_RECIPE_QUERY,
    variables: { title },
  });
};

export const getRecipe = async (id) => {
  const result = await gqlQuery(id);
  return result.data.getRecipe;
};

recipes.test.ts:

import { createMockClient } from 'mock-apollo-client';

const mockRecipe = {
  title: "Luke's Chocolate Orange",
  ingredients: [
    { name: 'Orange', amount: 1, unit: 'abc' },
    { name: 'Chocolate', amount: 250, unit: 'grams' },
  ],
  steps: ['Peel orange', 'Open chocolate', 'Eat chocolate', 'Throw orange away'],
};
const mockClient = createMockClient();

describe('api', () => {
  describe('recipes', () => {
    describe('Given a valid recipe id', () => {
      beforeEach(() => {
        jest.resetModules();
      });
      it('Should call query with the id as a param', async () => {
        jest.doMock('@apollo/client', () => {
          return {
            ...jest.requireActual('@apollo/client'),
            ApolloClient: jest.fn(() => mockClient),
          };
        });
        const queryHandler = jest.fn().mockResolvedValue({ data: { getRecipe: mockRecipe } });

        const { GET_RECIPE_QUERY, getRecipe } = require('./recipes');
        mockClient.setRequestHandler(GET_RECIPE_QUERY, queryHandler);

        const title = "Luke's Chocolate Orange";
        const result = await getRecipe(title);
        expect(result).toEqual(mockRecipe);
        expect(queryHandler).toBeCalledWith({ title });
      });
    });
  });
});

测试结果:

 PASS  src/Whosebug/71612556/recipes.test.ts
  api
    recipes
      Given a valid recipe id
        ✓ Should call query with the id as a param (91 ms)

------------------------|---------|----------|---------|---------|-------------------
File                    | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
------------------------|---------|----------|---------|---------|-------------------
All files               |   90.91 |      100 |   66.67 |   90.91 |                   
 mocks                  |      75 |      100 |       0 |      75 |                   
  handlers.js           |   66.67 |      100 |       0 |   66.67 | 14                
  server.js             |     100 |      100 |     100 |     100 |                   
 Whosebug/71612556 |     100 |      100 |     100 |     100 |                   
  recipes.ts            |     100 |      100 |     100 |     100 |                   
------------------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        2.775 s

你可以找到源代码here