如何使用 jest 测试带有片段的 GraphQL 查询
How to test GraphQL queries with fragments using jest
问题:我想像这样测试 .graphql
文件中的 GraphQL 查询:
#import '../../fragments/Widget.graphql'
query WidgetFragment($id: ID) {
readWidgetFragment(id: $id) {
...Widget
}
}
要使用模拟解析器和数据创建 GraphQL 模式,我使用 graphql-tools 中的 makeExecutableSchema
和 addMockFunctionsToSchema
。
对于运行来自笑话测试中的查询,我的理解是我需要使用来自graphql-js的graphql()
函数。
此函数需要将查询作为 字符串,因此我尝试了两种不同的方法,但均无效:
- 将
.graphql
文件解析为普通文本文件,给我原始字符串(在我的 jest 配置中使用 jest-raw-loader)。
这给了我: Failed: Errors in query: Unknown fragment "Widget".
当我 运行 查询时。
- 使用 jest-transform-graphql 将
.graphql
文件解析为 query
对象。我相信这应该是正确的方法,因为它应该 正确解析任何导入的片段。但是,要执行查询,我需要将 query.loc.source.body
传递给 graphql
,这会导致与选项 1. 相同的错误消息
你可以使用这个:
import { print } from 'graphql/language/printer'
import query from './query.gql'
...
print(query)
使用初始方法将其解析为原始文本,除了:
- 使用带路径参数的递归函数(假设您可以嵌套片段)
- 它使用 regex 预先将所有导入提取到数组中(也许使用更好的模式:))
- 将文件的其余部分附加到字符串变量
- 然后循环导入,解析
#import
s 并将它们传递给自身并将结果附加到字符串变量
- 最后 return 将结果传递给主函数,然后将其传递给
graphql()
是的,这是一个泡菜。即使导入正常工作(>= v2.1.0 for jest-transform-graphql,它们也会被添加到 query.definitions
对象中,在使用 document.loc.source.body
作为查询参数调用 graphql
时完全回避.
在服务器端,graphql (function graphqlImpl
) 将使用 parse(source)
重建 document
对象 - 但它对导入的片段定义的了解为零...
据我所知,最好的办法是在将片段发送到服务器之前将片段标记到查询源。您需要明确找到所有以 #import
开头的行,并将它们替换为要导入的 graphql
文件的实际文本内容。
下面是我使用的函数。 (未测试递归片段)
// Async wrapper around dynamic `import` function
import { importQuery } from "./queries";
const importAndReplace = async (fileToImport, sourceDocument, line) => {
const doc = await importQuery(fileToImport);
const targetDocument = (await sourceDocument).replace(line, doc.loc.source.body);
return targetDocument;
};
// Inspired by `graphql-tag/loader`
// Uses promises because of async function `importQuery` used
export default async graphqlOperation => {
const { body } = graphqlOperation.loc.source;
const lines = body.split(/\r\n|\r|\n/);
const bodyWithInlineImports = await lines.reduce(
async (accumulator, line) => {
await accumulator;
const lineSplit = line.slice(1).split(" ");
return line[0] === "#" && lineSplit[0] === "import"
? importAndReplace(lineSplit[1].replace(/"/g, ""), accumulator, line)
: Promise.resolve(accumulator);
},
Promise.resolve(body)
);
return bodyWithInlineImports;
};
问题:我想像这样测试 .graphql
文件中的 GraphQL 查询:
#import '../../fragments/Widget.graphql'
query WidgetFragment($id: ID) {
readWidgetFragment(id: $id) {
...Widget
}
}
要使用模拟解析器和数据创建 GraphQL 模式,我使用 graphql-tools 中的 makeExecutableSchema
和 addMockFunctionsToSchema
。
对于运行来自笑话测试中的查询,我的理解是我需要使用来自graphql-js的graphql()
函数。
此函数需要将查询作为 字符串,因此我尝试了两种不同的方法,但均无效:
- 将
.graphql
文件解析为普通文本文件,给我原始字符串(在我的 jest 配置中使用 jest-raw-loader)。 这给了我:Failed: Errors in query: Unknown fragment "Widget".
当我 运行 查询时。 - 使用 jest-transform-graphql 将
.graphql
文件解析为query
对象。我相信这应该是正确的方法,因为它应该 正确解析任何导入的片段。但是,要执行查询,我需要将query.loc.source.body
传递给graphql
,这会导致与选项 1. 相同的错误消息
你可以使用这个:
import { print } from 'graphql/language/printer'
import query from './query.gql'
...
print(query)
使用初始方法将其解析为原始文本,除了:
- 使用带路径参数的递归函数(假设您可以嵌套片段)
- 它使用 regex 预先将所有导入提取到数组中(也许使用更好的模式:))
- 将文件的其余部分附加到字符串变量
- 然后循环导入,解析
#import
s 并将它们传递给自身并将结果附加到字符串变量 - 最后 return 将结果传递给主函数,然后将其传递给
graphql()
是的,这是一个泡菜。即使导入正常工作(>= v2.1.0 for jest-transform-graphql,它们也会被添加到 query.definitions
对象中,在使用 document.loc.source.body
作为查询参数调用 graphql
时完全回避.
在服务器端,graphql (function graphqlImpl
) 将使用 parse(source)
重建 document
对象 - 但它对导入的片段定义的了解为零...
据我所知,最好的办法是在将片段发送到服务器之前将片段标记到查询源。您需要明确找到所有以 #import
开头的行,并将它们替换为要导入的 graphql
文件的实际文本内容。
下面是我使用的函数。 (未测试递归片段)
// Async wrapper around dynamic `import` function
import { importQuery } from "./queries";
const importAndReplace = async (fileToImport, sourceDocument, line) => {
const doc = await importQuery(fileToImport);
const targetDocument = (await sourceDocument).replace(line, doc.loc.source.body);
return targetDocument;
};
// Inspired by `graphql-tag/loader`
// Uses promises because of async function `importQuery` used
export default async graphqlOperation => {
const { body } = graphqlOperation.loc.source;
const lines = body.split(/\r\n|\r|\n/);
const bodyWithInlineImports = await lines.reduce(
async (accumulator, line) => {
await accumulator;
const lineSplit = line.slice(1).split(" ");
return line[0] === "#" && lineSplit[0] === "import"
? importAndReplace(lineSplit[1].replace(/"/g, ""), accumulator, line)
: Promise.resolve(accumulator);
},
Promise.resolve(body)
);
return bodyWithInlineImports;
};