feser/json_matcher

JSON匹配器集合,便于使用

0.2.3 2015-10-22 21:21 UTC

This package is not auto-updated.

Last update: 2024-09-23 12:21:13 UTC


README

Build Status Latest Stable Version Latest Unstable Version License Scrutinizer Code Quality Total Downloads

断言库,简化测试中对JSON数据和结构的验证。它不依赖于任何框架,因此您可以使用PhpUnit、PhpSpec、Peridot或您使用的任何框架。

为什么还需要另一个JSON断言库?

如果您尝试测试基于JSON的REST API,那么您可能遇到了几个问题

  • 您不能简单地检查响应是否等于给定的字符串,因为存在诸如服务器生成的ID和时间戳等问题。
  • API和预期JSON中的键顺序应该是相同的。
  • 匹配整个响应会破坏测试的DRY原则。

所有这些问题都可以通过两个简单的方法来解决:JSON规范化和匹配时排除键。这正是这个库所做的事情。它为您提供了在给定JSON中进行多步验证数据的方式,而不是一次性断言。

例如,我们正在为我们的API开发一个朋友列表功能。我们想要检查的是给定的用户是否在响应中,我们不想检查整个响应,这可以通过JSON模式验证或在其他测试用例中完成。

$alice = new User('Alice', 'Miller');
$john = new User('John', 'Smith');
$alice->addFriend($john);

$json = JsonMatcher::create(
    json_encode($alice->toArrayIncludingFriends()), ['id', 'created_at']
);

在上面的示例中,我们只创建了一个JsonMatcher实例,并指定了默认排除的键(idcreated_at)。将被排除的键将从JSON中删除,其值不会干扰等式断言。您也可以通过匹配excludingincluding选项来覆盖此键列表。

然后我们可以通过JSON路径检查John是否在Alice的朋友列表中的某个特定位置出现

$json->equal(json_encode($john->toArray()), ['at' => 'friends/0']);

或者如果我们不知道特定位置,我们只需检查John是否在我们的朋友列表中出现即可。

$json->includes(json_encode($john->toArray()), ['at' => 'friends']);

或者我们可以验证Alice的朋友列表中是否有任何John出现

$json->includes('{"first_name": "John"}'), ['at' => 'friends']);

入门

您可以通过composer安装此库

composer require fesor/json_matcher

然后您需要创建一个JsonMatcher实例。为此,您可以通过以下方式

  • 手动创建实例,设置所有依赖项和主题
  • 使用命名的构造函数JsonMatcher::create作为静态工厂方法。它将为您处理所有依赖项。
  • 使用JsonMatcherFactory。这在您的测试框架中具有某些IoC容器时很有用(例如Behat)。在这种情况下,您需要将此类注册为服务。

在匹配器构造函数中设置将被执行的断言主题。如果您想为每个断言重用相同的匹配器实例,只需通过setSubject方法更改主题即可。

示例

$jsonResponse = JsonMatcher::create($response->getContent());

// or you can use factory instead
$jsonResponse = $matcherFactory->create($response->getContent());

// and there you go, for example you may use something like this 
// for your gherkin steps implementations
$jsonResponse
    ->hasSize(1, ['at' => 'friends']) // checks that list of friends was incremented
    ->includes($friend, ['at' => 'friends']) // checks that correct record contained in collection
;

您可以在构造函数中将要排除的默认键列表作为第二个参数提供

$matcher = JsonMatcher::create($subject, ['id', 'created_at']);

请注意,默认情况下将忽略id键。

匹配器

所有匹配器都支持流畅接口、负匹配和一些选项。有关每个匹配器具有哪些选项的详细信息,请参阅详细描述。

equal

这是最常用的匹配器。您取两个JSON字符串进行比较。除了比较之前,此匹配器将规范化两个JSON字符串的结构,重新排序键,排除一些键(这是可配置的),然后简单地断言这两个字符串相等。您可以使用excluding选项指定要排除的键列表。

$actualJson = '["id": 1, "json": "spec"]';
$expectedJson = '["json": "spec"]';
$matcher
    ->setSubject($actualJson)
    ->equal($expectedJson, ['excluding' => ['id']])
;

如果您有一些键,这些键包含某些服务器生成的ID的一些时间相关值,在构建匹配器对象时指定默认排除的键列表会更方便。

$matcher = JsonMatcher::create($subject, ['id', 'created_at', 'updated_at']);

如果您想考虑这些键的值进行匹配,可以使用including选项指定包含的键列表。

$matcher = JsonMatcher::create($response->getContent(), ['id', 'created_at', 'updated_at']);
$jsonResponseSubject->equal($expectedJson, ['including' => ['id']]);

此外,您还可以通过at选项指定匹配应进行的JSON路径。我们稍后会回到这一点,因为所有匹配器都支持此选项。

包含

此匹配器的工作方式与equal匹配器略有不同。它所做的操作是递归扫描主题JSON并尝试找到任何JSON子集的包含。这在检查某个记录存在于集合中且您不知道或不想知道其具体路径的情况下很有用。

$json = <<<JSON
{
    "id": 1,
    "name": "Foo",
    "collection": [
        {"id": 1, "name": "Foo"},
        {"id": 2, "name": "Bar"},
    ]
}
JSON;

$matcher
    ->setSubject($json)
    // check for value inclusion
    ->includes('"Foo"')
    // checks is json subset presents in any item of collection
    ->includes('{"name": "Bar"}', ['at' => 'collection'])
    // checks is json presents in collection
    ->includes('{"name": "Bar", "value": "FooBar"}', ['at' => 'collection'])
;

由于此匹配器的工作方式与equal匹配器相同,它接受相同的选项。

hasPath

此匹配器检查给定的JSON是否具有特定路径。

$json = <<<JSON
{
    "collection": [
        "json",
        "matcher"
    ]
}
JSON;

$matcher
    ->setSubject($json)
    ->hasPath('collection/1')
;

hasSize

此匹配器检查给定的JSON中的集合是否包含特定数量的实体。

$json = <<<JSON
{
    "collection": [
        "json",
        "matcher"
    ]
}
JSON;

$matcher
    ->setSubject($json)
    ->hasSize(2, ['at' => 'collection'])
;

hasType

$json = <<<JSON
{
    "collection": [
        {},
        "json",
        42,
        13.45
    ]
}
JSON;

$matcher
    ->setSubject($json)
    ->hasType('array', ['at' => 'collection'])
    ->hasType('object', ['at' => 'collection/0'])
    ->hasType('string', ['at' => 'collection/1'])
    ->hasType('integer', ['at' => 'collection/2'])
    ->hasType('float', ['at' => 'collection/3'])
;

负匹配

要反转预期,只需使用带有not前缀的匹配器方法即可。

$matcher
    ->setSubject($json)
    ->notEqual($expected)
    ->notIncludes($part)
;

Json Path

所有方法都具有一个选项,指定应执行匹配的路径。例如

$actual = <<<JSON
{
    "collection": [
        "item"
    ]
}
JSON;
$expected = '"item"';
JsonMatcher::create($actual)
    ->equal($expected, ['at' => 'collection/0'])
;

贡献

请欢迎您贡献!