阿波罗的 MockedProvider 没有明确警告缺少模拟
apollo's MockedProvider doesn't warn clearly about missing mocks
我正在使用 apollo 挂钩(useQuery
、useMutation
)对 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 代码正在编译,但我没有收到回复,因为我的一个类型有点不对劲(也许我发送了额外的字段,我不记得了)。
我正在使用 apollo 挂钩(useQuery
、useMutation
)对 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 代码正在编译,但我没有收到回复,因为我的一个类型有点不对劲(也许我发送了额外的字段,我不记得了)。