@4c/graphql-mocking 中文文档教程

发布于 3年前 浏览 28 项目主页 更新于 3年前

GraphQL Mocking

使用示例快速模拟任何 GraphQL API,并使用智能自动生成的数据进行扩充。

Overview

在高级别 graphql-mocking 只是一个 graphql-js “服务器”,处理 请求并解析返回值。 而不是从 API 获取数据 或其他后端,它使用从构建的内存 graphql(如简单的 ORM) 示例数据和从类型信息生成的数据。

graphql-mocking 的核心导出是一个MockStore 实例,它封装了 模拟的 GraphQL 模式和内存数据。

import Mocks from '@4c/graphql-mocking';
import graphql from from 'graphql';

const store = new Mocks(`
  type Root {
    person(id: ID): Person
  }

  type Person {
    name: String
  }

  schema {
    query: Root
  }
`)

const result = await graphql(store.mockedSchema, `
  query {
    person(id: 1) {
      name
    }
  }
`);

无需任何额外配置 mockedSchema 将解析有效 使用种子生成的数据进行查询。 更好的是,一些常见的 schema 模式会自动实现,比如 Relay style 与分页的连接。

How data is generated.

由于测试由 GraphQL 服务器支持的 UI 是一个主要用例。 它不是 足以简单地生成随机数据。 每次运行都会改变的数据 导致糟糕的测试。 为了避免这种情况,每个字段都可以访问“种子” 数据生成器,这意味着数据将持续生成 每次运行解决方案时的字段。

Customizing mocks

通常完全生成的数据不足以进行大多数模拟。 最终 你想添加具体的例子和自定义模拟。 为了实现这一点,我们 需要引入两个概念:

  • Mocks
  • Examples

Mocks

Mocks 控制模式解析逻辑。 他们很相似 本着 GraphQL 字段解析器的精神,期望它们有不同的“枢轴”。 通常,解析器是每个字段 定义的。 一个模式有很多不同的 具有 Person 字段类型的类型,每个类型都定义了自己的解析器 从源对象。 模拟,按类型 工作,这意味着您可以定义 *any Person 是如何解析的,而不管它的父类型。

这是定义架构行为的强大方法,无需 阐明模式中类型的每次使用的行为。 例如 我们可以实现对带参数的字段的查找:

// fake data
const people = {
  1: { name: 'James' },
  2: { name: 'Besty' },
};

// Mock the 'person' field on the tooy Query type
store.mock('Query', () => ({
  person: (args, context, info) => people[args.id],
}));

const result = await graphql(
  store.mockedSchema,
  gql`
    query {
      person(id: 1) {
        name
      }
    }
  `,
);

result.data.person; // { name: 'James' }

Mocks 返回一个“源”对象,GraphQL 使用它来解析 类型的值,你在嘲笑。 有关此内容的概述 需要我们建议阅读:https://graphql.org/graphql-js/object-types/ 但是很快,模拟可以返回一个具体的值,就像标量的情况一样 像 StringBoolean 等。 具体价值。

store.mock('String', () => 'hello world'); // 'hello world' will be used for all strings

store.mock('Person', () => ({
  name: () => generateName(),
}));

如上面的人物示例所示。 模拟字段解析器也基于 字段参数以及 graphql contextinfo 对象。

Examples

示例是 graphql 类型的静态数据,将其视为来自 为 GraphQL 解析提供源对象的数据库。 为了 实例 以下是来自我们模式的 Person 的“示例”:

const people = store.addExamples('Person', [
  {
    id: 'a575bf7b-3eda-4015-98f9-6077a68a91e8',
    name: 'James',
    age: 68,
  },
  {
    id: 'e78ac19b-6d06-401e-9ff4-6a4f20dea718',
    name: 'Betsy',
    age: 42,
  },
]);

当您添加示例时,它们被用作一个池以从中提取 解析类型。 例子不需要很全面, GQL 类型中没有相应示例的任何字段 字段将正常生成。

示例通常在结构上不同于它们解析的 GraphQL 类型! 例如我们的 Person 可能看起来像:

type Person {
  id: ID
  personId: String
  name: String!
  isRetirementAge: Boolean
}

这里不是直接暴露 age,Person 定义了 isRetirementAge 来源于年龄。 但是,当我们尝试添加一个带有 age 的示例时,我们得到 一个错误:

store.addExample('Person', {
  id: 'e78ac19b-6d06-401e-9ff4-6a4f20dea718',
  name: 'Betsy',
  age: 42,
});

// TypeError: `age does not exist on type Person`

这有助于确保我们的模拟数据是明确的 关于哪些属性映射到 GraphQL 字段。 如果我们想 明确偏离我们需要在我们的字段前加上 $ 来标记它 作为“内部”。

store.addExample('Person', {
  id: 'e78ac19b-6d06-401e-9ff4-6a4f20dea718',
  name: 'Betsy',

  $age: 42,
});

现在我们可以将我们的示例与 Mock 配对以得出正确的 isRetirementAge 的值。

const store = new Mocks(schema);

store.addExample('Person', {
  id: 'e78ac19b-6d06-401e-9ff4-6a4f20dea718',
  name: 'Betsy',
  age: 42,
});

store.mock('Person', () => ({
  isRetirementAge() {
    // this is the source object in a graphql resolver
    this.$age >= 65;
  },
}));

Defining a graph

示例为模拟模式提供了要使用的具体值 在解析类型时,以及定义之间的关系时 图中的数据。 与数据库一样,示例应提供 主键(默认为id$id)。 Pks 用于创建显式 图中其他示例之间的关系。

考虑以下定义 Person 和博客 Post 的模式:

type Post {
  id: ID
  title: String
  content: String
}

type Person {
  id: ID
  name: String!
  posts: [Post]
}

如果我们想添加定义链接的示例,我们可以这样做:

store.addExample('Person', [
  {
    id: 'person1',
    name: 'James',
  },
  {
    id: 'person2',
    name: 'Betsy',
  },
]);

store.addExamples('Post', [
  {
    id: 'post1',
    $personId: 'person1',
    title: 'Building a graphql mocking library',
  },
  {
    id: 'post2',
    $personId: 'person1',
    title: 'Funny looking birds',
  },
  {
    id: 'post3',
    $personId: 'person2',
    title: 'The Ultimate Answer',
  },
]);

现在我们可以将这两种类型与模拟相关联,使用 现在,当我们查询有关人员的帖子时,内置的 related 帮助

import { related } from '@4c/graphql-mocking';

store.mock('Person', () => ({
  posts: related({
    relatedFieldName: '$personId',
    idField: '$id',
  }),
}));

程序将“正常工作”

const result = await graphql(
  store.mockedSchema,
  gql`
    query {
      person(id: "person1") {
        name
        posts {
          title
        }
      }
    }
  `,
);
// results in
data: {
  person: {
    name: 'James',
    posts: [
      { title: 'Building a graphql mocking library' },
      { title: 'Funny looking birds' }
    ]
  }
}

(这同样适用于一对多或一对一关系)。

因为这是一个常见的模式,库会自动设置 如果可以从示例和类型中推断出这些关系。

Heads:以 Id 结尾的内部键是自动的 被视为连接类型的外键

模拟也足够聪明,可以将字段推断为外键 如果字段的模式类型是对象类型并且示例值 是一个字符串,它将假定它是一个 id 引用。

store.addExamples('Post', [
  {
    $id: 'post1',
    person: 'person1',
    title: 'Building a graphql mocking library',
  },

无需其他配置。

Connections and Relay

graphql-mocking,开箱即用地支持中继模式添加, 其中包括:

Node Interface

当模式具有 Node 接口和 node 查询字段时,graphql-mocking 将自动模拟它们以使用示例数据。

query {
  node(id: "RmlsbToy") {
    ... on Person {
      name
    }
  }
}

Global IDs

此外,专门的 ID 标量模拟被配置为返回中继兼容的“全局 ID”, 这是类型名称和本地标识符的 base64 编码。 注意这需要 示例使用与 id 不同的 id 字段,我们推荐 $id 因为它可以解决 的盒子。

store.addExample('Person', [
  {
    $id: 'person1',
    name: 'James',
  },
  {
    $id: 'person2',
    name: 'Betsy',
  },
]);

const result = await graphql(
  store.mockedSchema,
  gql`
    query {
      people {
        name
      }
    }
  `,
);

Connections

带连接的分页也像 List 类型一样开箱即用 生成和推理。 也可以通过以下方式直接配置连接:

import { connection } from '@4c/graphql-mocking/relay';

store.mock('Person', {
  postConnection: connection({ relatedIdField: '$personId' }),
});

GraphQL Mocking

Quickly mock out any GraphQL API with examples and augmented with smart autogenerated data.

Overview

At a high level graphql-mocking is just a graphql-js "server" that processes a request and resolves an return value. Instead of sourcing the data from an API or other backend, it uses an in memory graphql (like a simple ORM) built from example data and data generated from type information.

The core export of graphql-mocking is a MockStore instance which encapsulates a mocked GraphQL schema and the in memory data.

import Mocks from '@4c/graphql-mocking';
import graphql from from 'graphql';

const store = new Mocks(`
  type Root {
    person(id: ID): Person
  }

  type Person {
    name: String
  }

  schema {
    query: Root
  }
`)

const result = await graphql(store.mockedSchema, `
  query {
    person(id: 1) {
      name
    }
  }
`);

Without any additional configuration mockedSchema will resolve valid queries with seed generated data. Even better, a number of common schema patterns will be implemented automatically, such as Relay style Connections with pagination.

How data is generated.

Since testing UIs backed by a GraphQL server is a main use case. It's not sufficient to simply generate randon data. Data that changes every run makes for bad tests. To avoid this each field has access to a "seeded" data generator, which means data will be consistently generated for that field every time a resolution is run.

Customizing mocks

Generally fully generated data isn't sufficient for most mocking. Eventually you want to add specific examples and custom mocking. To accomplish this we need to introduce two concepts:

  • Mocks
  • Examples

Mocks

Mocks control schema resolution logic. They are similar in spirit to a GraphQL field resolver, expect they have a different "pivot". Normally a resolver is defined per field. A schema have many different types with the Person field type, and each one defines it's own resolver from the source object. Mocks, work per type instead, meaning you can define how *any Person is resolved regardless of it's parent type.

This is a powerful way to define schema behavior without needing to clarify behavior for every usage of a type in your schema. For instance we can implement a lookup for fields with arguments:

// fake data
const people = {
  1: { name: 'James' },
  2: { name: 'Besty' },
};

// Mock the 'person' field on the tooy Query type
store.mock('Query', () => ({
  person: (args, context, info) => people[args.id],
}));

const result = await graphql(
  store.mockedSchema,
  gql`
    query {
      person(id: 1) {
        name
      }
    }
  `,
);

result.data.person; // { name: 'James' }

Mocks return a "source" object used by GraphQL to resolve the value of the type, you are mocking. For an overview of what this entails we suggest reading: https://graphql.org/graphql-js/object-types/ but quickly, mocks can return a concrete value, as in the case of scalars like String, Boolean, etc. Or an object with functions that return a concrete value.

store.mock('String', () => 'hello world'); // 'hello world' will be used for all strings

store.mock('Person', () => ({
  name: () => generateName(),
}));

AS seen in the person example above. Mock field resolvers are also based the field arguments as well as the graphql context and info objects.

Examples

Examples are static data for a graphql type, think of it as the data from your database that provides the source objects for GraphQL resolution. For instance the following are "examples" of a Person from our schema:

const people = store.addExamples('Person', [
  {
    id: 'a575bf7b-3eda-4015-98f9-6077a68a91e8',
    name: 'James',
    age: 68,
  },
  {
    id: 'e78ac19b-6d06-401e-9ff4-6a4f20dea718',
    name: 'Betsy',
    age: 42,
  },
]);

When you add examples, they are used as a pool to pull from when resolving types. Examples don't need to be conprehensive, any fields in the GQL type that don't have a corresponding example field will be generated normally.

Examples often differ structurally from the GraphQL type they resolve to! For instance our Person might look like:

type Person {
  id: ID
  personId: String
  name: String!
  isRetirementAge: Boolean
}

Here instead of exposing age directly, Person defines isRetirementAge which is derived from age. However, when we try and add an example with age we get an error:

store.addExample('Person', {
  id: 'e78ac19b-6d06-401e-9ff4-6a4f20dea718',
  name: 'Betsy',
  age: 42,
});

// TypeError: `age does not exist on type Person`

This is helpful guardrail to ensure that our mock data is explicit about which properties map to GraphQL fields. If we want to explicitly deviate we need to prefix our field with $ to mark it as "internal".

store.addExample('Person', {
  id: 'e78ac19b-6d06-401e-9ff4-6a4f20dea718',
  name: 'Betsy',

  $age: 42,
});

Now we can pair our example with a Mock to derive the correct value for isRetirementAge.

const store = new Mocks(schema);

store.addExample('Person', {
  id: 'e78ac19b-6d06-401e-9ff4-6a4f20dea718',
  name: 'Betsy',
  age: 42,
});

store.mock('Person', () => ({
  isRetirementAge() {
    // this is the source object in a graphql resolver
    this.$age >= 65;
  },
}));

Defining a graph

Examples provide the mocked schema with concrete values to use when resolving types, as well as defining relationships between data in the graph. As with databases, examples should provide a primary key (by default either id or $id). Pks are used to create explicit relationships between other examples in the graph.

Consider the following schema defniing Person and blog Post:

type Post {
  id: ID
  title: String
  content: String
}

type Person {
  id: ID
  name: String!
  posts: [Post]
}

If we wanted to add examples that defined links we could do so like:

store.addExample('Person', [
  {
    id: 'person1',
    name: 'James',
  },
  {
    id: 'person2',
    name: 'Betsy',
  },
]);

store.addExamples('Post', [
  {
    id: 'post1',
    $personId: 'person1',
    title: 'Building a graphql mocking library',
  },
  {
    id: 'post2',
    $personId: 'person1',
    title: 'Funny looking birds',
  },
  {
    id: 'post3',
    $personId: 'person2',
    title: 'The Ultimate Answer',
  },
]);

Now we can relate these two types with a mock using the built-in related helper

import { related } from '@4c/graphql-mocking';

store.mock('Person', () => ({
  posts: related({
    relatedFieldName: '$personId',
    idField: '$id',
  }),
}));

now when we query for posts on people it will "Just Work"

const result = await graphql(
  store.mockedSchema,
  gql`
    query {
      person(id: "person1") {
        name
        posts {
          title
        }
      }
    }
  `,
);
// results in
data: {
  person: {
    name: 'James',
    posts: [
      { title: 'Building a graphql mocking library' },
      { title: 'Funny looking birds' }
    ]
  }
}

(This works for one-to-many or one-to-one relationships equally well).

Because this is such a common pattern, the library will automatically set up these relationships if it can infer from the example and type.

Heads: Internal keys that end with Id are automatically considered foreign key to it's connected type.

The mocking is also smart enough to infer fields as foreign keys if the schema type for the field is an object type and the example value is a string, it will assume it's an id reference.

store.addExamples('Post', [
  {
    $id: 'post1',
    person: 'person1',
    title: 'Building a graphql mocking library',
  },

No other configuration needed.

Connections and Relay

graphql-mocking, comes with out of the box support for Relay schema additions, which include:

Node Interface

When the schema has a Node interface and node query field, graphql-mocking will automatically mock them to work with example data.

query {
  node(id: "RmlsbToy") {
    ... on Person {
      name
    }
  }
}

Global IDs

In addition a specialized ID scalar mock is configured to return Relay compatible "global Ids", which are base64 encodings of the type name and local identifier. Note this requires that examples use a different field for their id than id, we recommend $id since it works out of the box.

store.addExample('Person', [
  {
    $id: 'person1',
    name: 'James',
  },
  {
    $id: 'person2',
    name: 'Betsy',
  },
]);

const result = await graphql(
  store.mockedSchema,
  gql`
    query {
      people {
        name
      }
    }
  `,
);

Connections

Pagination with connections also works out of the box just like List type generation and inference. Connections can also be configured directly via:

import { connection } from '@4c/graphql-mocking/relay';

store.mock('Person', {
  postConnection: connection({ relatedIdField: '$personId' }),
});
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文