Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Jsontestsuite Laravel Package

nst/jsontestsuite

A Laravel/PHP testing package for JSON APIs, offering fluent, expressive assertions for response structure, fragments, paths, and values. Write clearer, shorter API tests with less boilerplate and more confidence in your JSON contracts.

Deep Wiki
Context7

Getting Started

nst/jsontestsuite is not a PHP library — it's a corpus of ~300 handcrafted JSON documents plus a reference runner. You consume it as test fixtures, not as a dependency you call. That distinction shapes everything below.

Vendor the corpus, don't autoload it. There is no meaningful Composer entry point; pull the test_parsing/ directory in one of three ways:

# Option A — git submodule (keeps provenance, easy to bump)
git submodule add https://github.com/nst/JSONTestSuite.git tests/fixtures/JSONTestSuite

# Option B — one-shot copy into your repo, pinned by commit
curl -L https://github.com/nst/JSONTestSuite/archive/<sha>.tar.gz | tar xz \
  && mv JSONTestSuite-*/test_parsing tests/fixtures/json-testsuite

# Option C — a small script in composer.json "scripts" that re-downloads on demand

Add tests/fixtures/ to .gitattributes with export-ignore so the corpus never ships in a deploy artifact.

Look first at three things:

  1. The README table describing the prefixes — this is the whole contract: y_ must parse, n_ must fail, i_ is implementation-defined (either outcome is legal, but a crash or hang is not).
  2. parsing_test.py — ~40 lines showing the canonical pass/fail loop; translate it to PHPUnit verbatim.
  3. results/parsing/ — pre-recorded outcomes for dozens of parsers. Useful for calibrating what "normal" looks like before you decide a failure is your bug.

First use case — a smoke test for ext-json on your pinned PHP version:

// tests/Unit/Json/JsonTestSuiteTest.php
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;

class JsonTestSuiteTest extends TestCase
{
    private const DIR = __DIR__ . '/../../fixtures/json-testsuite';

    public static function validJson(): iterable
    {
        foreach (glob(self::DIR . '/y_*.json') as $f) {
            yield basename($f) => [$f];
        }
    }

    #[DataProvider('validJson')]
    public function test_accepts_valid_documents(string $path): void
    {
        $raw = file_get_contents($path);
        json_decode($raw, true, 512);
        $this->assertSame(JSON_ERROR_NONE, json_last_error(), json_last_error_msg());
    }
}

That single test class typically takes under a second and immediately tells you whether a PHP upgrade changed parser behavior — which is the reason most teams adopt the corpus in the first place.

Implementation Patterns

Data providers, three of them. Keep y_, n_, and i_ as separate providers so failures read correctly in CI output. For n_, assert json_last_error() !== JSON_ERROR_NONE — never assert on null return, because null is a valid JSON document.

Test your wrapper, not the standard library. If you have a JsonDecoder service, an ApiResponse::json() helper, or a JsonRequest form request, run the corpus against that, not against bare json_decode. The suite only earns its keep when it exercises the layer your application actually calls.

Validate through the HTTP boundary for the highest-value cases. The n_ corpus makes an excellent negative-input fuzz set for any endpoint that accepts raw JSON:

public static function invalidJson(): iterable { /* glob n_*.json */ }

#[DataProvider('invalidJson')]
public function test_endpoint_rejects_malformed_json(string $path): void
{
    $this->call('POST', '/api/events', [], [], [], [
        'CONTENT_TYPE' => 'application/json',
    ], file_get_contents($path))->assertStatus(422);
}

This catches the classic Laravel failure mode where $request->json() silently swallows the parse error and hands your controller an empty array, producing a 200 with an empty record instead of a 422.

Golden-file the i_ set. Implementation-defined cases are where PHP quietly changes its mind between minor versions. Record the current outcome for each i_ file into a committed tests/fixtures/json-i-expected.php map and assert against it. Failures then become "PHP changed behavior" rather than "the test suite is red."

Round-trip the y_ set. decode → encode → decode and assert structural equality on the result. This finds float precision drift (1E400 → INF → 0), which hits anyone storing monetary or scientific values as JSON blobs.

Test third-party parsers for interchangeability. If different services in your stack use ext-json, justinrainbow/json-schema, and opis/json, run the same corpus across all three. Divergences on y_ or n_ are latent bugs in any service mesh that forwards payloads between them.

CI integration. One job, one PHP-version matrix. Cache the corpus download. Treat y_/n_ failures as hard, i_ drift as a soft warning that requires a reviewer's sign-off on the golden-file diff.

Gotchas and Tips

Read files as bytes. file_get_contents, not file(), not fgets. Several n_ cases hinge on a BOM, a null byte, or a missing trailing newline; anything that normalizes line endings or strips a BOM converts a failing test into a passing one and silently weakens your suite.

json_decode returns null for the valid document null. Every n_ assertion must pair the return check with json_last_error(). This is the single most common bug when people first port parsing_test.py.

Depth limits will bite you. Some structural cases nest beyond PHP's default 512. Pass the depth argument explicitly in your helper so the failure message points at the document, not at an opaque JSON_ERROR_DEPTH.

PHP's ext-json is not strictly RFC 8259. It accepts lone surrogates in \u escapes (replacing them with U+FFFD) and is lenient about a few number/string edge cases. Those are i_ files — expect them, don't try to "fix" them, and don't let a teammate harden your validator by flipping a y_ case into an n_ assertion.

Timeouts are part of correctness. A handful of pathological inputs are designed to blow up naive recursive-descent parsers. If you're testing a hand-rolled parser, wrap the call with a wall-clock budget (pcntl_alarm or a pcntl_fork guard) — a hang is a failure, not an inconclusive test.

Keep the corpus out of your application namespace. No PSR-4 mapping, no composer.json autoload entry, no vendor/ exposure. It's fixtures. Treating it as a library invites someone to use from it in production code.

Extension point: add an x_ prefix. The prefix scheme is trivially extensible. Drop app-specific documents into tests/fixtures/json-testsuite-app/x_*.json and have your provider glob both directories. This is how you turn a generic compliance suite into a regression suite for the exact payload shapes your product has historically choked on.

Bumping the corpus is a review-worthy commit. The upstream repo gains files over time; a git submodule update can turn a green build red purely because a new n_ case surfaced a real parser bug. Never auto-bump in CI — pin the SHA and upgrade deliberately.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource