duxet/json_spec

使用 PhpSpec 和 Behat 轻松处理 JSON 结构

1.3.0 2022-08-22 15:36 UTC

This package is auto-updated.

Last update: 2024-09-22 20:14:32 UTC


README

Scrutinizer Code Quality Build Status

Json Spec 提供了一套易于使用的匹配器,可以帮助您在 API 的 JSON 响应中轻松验证数据,减少痛苦。

如果您正在处理基于 JSON 的 RESTful API,会遇到一些问题

  • 您不能简单地将响应与给定的字符串进行比较,因为还有像服务器生成的 ID 或键排序这样的问题。
  • API 和预期的 JSON 的键顺序应该相同。
  • 匹配整个响应会破坏 spec 的 DRY 原则

json_spec 通过在匹配之前对 JSON 进行归一化来解决这些问题。

让我们看看一个简单的例子

json_spec 会假设这些 JSON 文档是相等的。在断言之前,json_spec 会从响应 JSON 中排除 idcreated_atupdated_at 等键(排除的键列表是可配置的)。然后它会对 JSON 进行归一化(重新排序键、美化打印)然后,只需检查字符串相等性即可。这就是全部。您还可以通过给定的路径匹配 JSON,而不是在规范中描述整个响应,检查 JSON 集合是否包含某些记录等。

安装

要安装 json_spec,您可能想使用 composer

composer require --dev fesor/json_spec

之后,您应该启用 json_spec behat 扩展,并在 behat.yml 中可选地添加 json_spec 提供的上下文。例如

default:
    suites:
        default:
            contexts:
                - FeatureContext
                - json_spec
    extensions:
        JsonSpec\Behat\Extension: ~

使用方法

json_spec 提供两种使用匹配器的方式

  • 使用 json_spec 上下文,它实现了用于验证 JSON 响应的步骤。这种方法最适合开发人员使用功能规范作为 API 文档和测试的情况。
  • JsonMatcherFactory 注入到您的上下文中,以便您可以在步骤定义中使用它。这种方法更可取,因为它允许您使用它来实践 BDD。有关如何编写功能规范的更多信息,请参阅 Modeling by examples

使用 json_spec 上下文

json_spec 提供了 Behat 上下文,该上下文实现了利用 json_matcher 提供的所有匹配器的步骤。这对于测试您的应用的 JSON API 完美。

注意一点。json_spec 应该可以访问响应。如果您正在使用 Mink,那就没问题。json_spec 会从 Mink 获取响应。这意味着您要开始工作,只需要在 behat.yml 中启用 MinkExtension。

default:
    suites:
        default:
            contexts:
                - FeatureContext
                - json_spec
    extensions:
        JsonSpec\Behat\Extension: ~
        Behat\MinkExtension:
            base_url:  'https://:8047'
            sessions:
                default:
                    goutte: ~

这就完成了,现在 json_spec 可以访问所有响应。您还可以使用 sanpii/behatch-contexts 中的 behatch:rest 上下文来代替 Mink 上下文。

如果您正在使用自己的上下文,该上下文不使用 Mink,则只需为您的上下文实现 JsonHolderAware 接口。

use \JsonSpec\Behat\Context\JsonHolderAware;
use \Behat\Behat\Context\Context;

class MyRestApiFeatureContext implements Context, JsonHolderAware
{
    /**
     * @var \JsonSpec\Behat\JsonProvider\JsonHolder
     */
    private $jsonHolder;

    /**
     * @When /^I request "([^"]*)"$/
     */
    public function iRequest($pageUrl)
    {
        // ... make request and get response body as string
        $this->jsonHolder->setJson($responseBody);
    }
}

现在,您可以在功能中使用 json_spec 步骤

Feature: User API
  Background:
    Given the following users exist:
      | id | first_name | last_name |
      | 1  | Steve      | Richert   |
      | 2  | Catie      | Richert   |
    And "Steve Richert" is friends with "Catie Richert"

  Scenario: Index action
    When I visit "/users.json"
    Then the JSON response should have 2 users
    And the JSON response at "0/id" should be 1
    And the JSON response at "1/id" should be 2

  Scenario: Show action
    When I visit "/users/1.json"
    Then the JSON response at "first_name" should be "Steve"
    And the JSON response at "last_name" should be "Richert"
    And the JSON response should have "created_at"
    And the JSON response at "created_at" should be a string
    And the JSON response at "friends" should be:
      """
      [
        {
          "id": 2,
          "first_name": "Catie",
          "last_name": "Richert"
        }
      ]
      """

上面的背景步骤和“访问”步骤不是由 json_spec 提供的。其余步骤由 json_spec 提供。它们功能强大,可以用于多种不同的格式

Then the JSON should be:
  """
  {
    "key": "value"
  }
  """
Then the JSON at "path" should be:
  """
  [
    "entry",
    "entry"
  ]
  """

Then the JSON should be {"key":"value"}
Then the JSON at "path" should be {"key":"value"}
Then the JSON should be ["entry","entry"]
Then the JSON at "path" should be ["entry","entry"]
Then the JSON at "path" should be "string"
Then the JSON at "path" should be 10
Then the JSON at "path" should be 10.0
Then the JSON at "path" should be 1e+1
Then the JSON at "path" should be true
Then the JSON at "path" should be false
Then the JSON at "path" should be null

Then the JSON should include:
  """
  {
    "key": "value"
  }
  """
Then the JSON at "path" should include:
  """
  [
    "entry",
    "entry"
  ]
  """

Then the JSON should include {"key":"value"}
Then the JSON at "path" should include {"key":"value"}
Then the JSON should include ["entry","entry"]
Then the JSON at "path" should include ["entry","entry"]
Then the JSON should include "string"
Then the JSON at "path" should include "string"
Then the JSON should include 10
Then the JSON at "path" should include 10
Then the JSON should include 10.0
Then the JSON at "path" should include 10.0
Then the JSON should include 1e+1
Then the JSON at "path" should include 1e+1
Then the JSON should include true
Then the JSON at "path" should include true
Then the JSON should include false
Then the JSON at "path" should include false
Then the JSON should include null
Then the JSON at "path" should include null

Then the JSON should have "path"

Then the JSON should be a hash
Then the JSON at "path" should be an array
Then the JSON at "path" should be a float

Then the JSON should have 1 entry
Then the JSON at "path" should have 2 entries
Then the JSON should have 3 keys
Then the JSON should have 4 whatevers

上面所有“应该”的实例都可以跟“不”一起使用,所有“JSON”的实例都可以小写,并且可以跟“response”一起使用。

表格格式

另一个步骤使用 Behat 的表格格式并包装了上述两个步骤

然后 JSON 应该有如下内容

  | path/0 | {"key":"value"}   |
  | path/1 | ["entry","entry"] |

可以给出任意数量的行。上述步骤相当于

Then the JSON at "path/0" should be {"key":"value"}
And the JSON at "path/1" should be ["entry","entry"]

如果只给出了一列

Then the JSON should have the following:
  | path/0 |
  | path/1 |

这等价于

Then the JSON should have "path/0"
And the JSON should have "path/1"

JSON内存

还有一步Behat是json_spec提供的,上面没有使用。它用于记住JSON以便在后续步骤中重复使用。你可以通过提供一个名称来“保存”所有或部分JSON。

Feature: User API
  Scenario: Index action includes full user JSON
    Given the following user exists:
      | id | first_name | last_name |
      | 1  | Steve      | Richert   |
    And I visit "/users/1.json"
    And I keep the JSON response as "USER_1"
    When I visit "/users.json"
    Then the JSON response should be:
      """
      [
        {$USER_1}
      ]
      """

你可以在路径中记住JSON

Given I keep the JSON response at "first_name" as "FIRST_NAME"

你可以在路径中记住JSON

Then the JSON response at "0/first_name" should be:
  """
  {$FIRST_NAME}
  """

你还可以在行内记住JSON

Then the JSON response at "0/first_name" should be {$FIRST_NAME}

从版本0.2.3开始,你可以将内存辅助器注入到你的功能上下文中以定义一些变量。为此,你的上下文应该实现MemoryHelperAware接口。

更多示例

查看功能,以查看您可以使用json_spec的所有不同方式。

在步骤定义中使用json匹配器

要注入JsonMatcherFactory,你只需在上下文中实现JsonMatcherAware接口,或者直接使用JsonMatcherAwareTrait

Scenario: User should be able to add another user in friend list
    Given I signed in as Bob
      And I view Alice's profile
    When I add her to my friends list
    Then Alice should appear in my friend list
use JsonMatcherAwareTrait;

/**
 * @Then :user should appear in my friend list
 */
function userShouldAppearInFriendList(User $user)
{
    $this
       ->json($this->lastResponse)
       ->haveSize(1)
       ->includes($this->serialize($user, ['short_profile'])
    ;
}

贡献

如果你遇到任何问题,请告诉我 。欢迎提交带有测试的pull请求。没有pull请求太小。请帮助以下方面

  • 报告错误
  • 建议功能
  • 编写或改进文档
  • 修复错别字
  • 清理空白
  • 重构代码
  • 添加测试
  • 关闭问题

如果你报告了一个错误但没有包括修复,请包括一个失败的测试。

鸣谢

  • json_spec - RSpec和Cucumber中处理JSON的Ruby gem。这个库主要是这个优秀库的PHP移植。