模拟阿波罗客户端返回 "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
我正在编写一个玩具应用程序来了解有关无服务器框架和 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