# Test Assertions

# Introduction

This chapter provides details of the available JSON:API response assertions. Our test assertions are built to provide detailed and human-readable JSON diffs, so you can spot exactly where a JSON:API document differs from the expected JSON.

# Resource Objects

You should use the following assertions when you expect zero-to-one resource object in the response:

# assertFetchedOne

The assertFetchedOne method asserts that:

  • The response has a 200 OK response status;
  • The response content type is application/vnd.api+json; AND
  • The data member of the JSON:API document matches the expected resource.

You can provide either an array representation of the expected resource, or a model.

Providing a model just matches the resource based on the expected type and the model's route key.

If providing an array, you can provide a partial representation of the expected resource - i.e. you do not need to provide every attribute, relationship and link. The assertion will just match those you provided.

For example:

use App\Models\Post;

$model = Post::factory()->create();

$response = $this
    ->jsonApi()
    ->expects('posts')
    ->get('/api/v1/posts/' . $post->getRouteKey());

$response->assertFetchedOne($model);

// or...

$response->assertFetchedOne([
    'type' => 'posts',
    'id' => (string) $post->getRouteKey(),
    'attributes' => [
        'slug' => $post->slug,
        'title' => $post->title,
    ],
]);

# assertFetchedOneExact

The assertFetchedOneExact method asserts that:

  • The response has a 200 OK response status;
  • The response content type is application/vnd.api+json; AND
  • The data member of the JSON:API document exactly matches the provided array.

When providing the expected array, you will need to provide the entire expected resource object. For example:

use App\Models\Post;

$model = Post::factory()->create();

$uri = "http://localhost/api/v1/posts/{$post->getRouteKey()}";

$response = $this
    ->jsonApi()
    ->expects('posts')
    ->get($uri);

$response->assertFetchedOneExact([
    'type' => 'posts',
    'id' => (string) $post->getRouteKey(),
    'attributes' => [
        'content' => $post->content,
        'createdAt' => $post->created_at,
        'slug' => $post->slug,
        'title' => $post->title,
        'updatedAt' => $post->updated_at,
    ],
    'relationships' => [
        'author' => [
            'links' => [
                'self' => "{$uri}/relationships/author",
                'related' => "{$uri}/author",
            ],
        ],
        'tags' => [
            'links' => [
                'self' => "{$uri}/relationships/tags",
                'related' => "{$uri}/tags",
            ],
        ],
    ],
    'links' => [
        'self' => $uri,
    ],
]);

# assertFetchedNull

The assertFetchedNull method asserts that:

  • The response has a 200 OK response status;
  • The response content type is application/vnd.api+json; AND
  • The data member of the JSON:API document is null.

For example:


$model = Post::factory()->create(['slug' => 'foo-bar']);

$response = $this
    ->jsonApi()
    ->expects('posts')
    ->filter(['slug' => 'baz-bat'])
    ->get('/api/v1/posts/' . $post->getRouteKey());

$response->assertFetchedNull();

# assertCreatedWithServerId

The assertCreatedWithServerId method asserts that:

  • The response has a 201 Created response status;
  • The response content type is application/vnd.api+json;
  • The response has a Location header set to the resource's URI; AND
  • The data member of the JSON:API document has the expected resource object.

The first argument to the method is the expected Location URI. As the server issues the id, you provide the expected URI without an ID. The second argument is an array of the expected resource object.

For example:

$post = Post::factory()->make();

$data = [
    'type' => 'posts',
    'attributes' => [
        'content' => $post->content,
        'slug' => $post->slug,
        'title' => $post->title,
    ],
];

$response = $this
    ->jsonApi()
    ->expects('posts')
    ->withData($data)
    ->post('/api/v1/posts', $data);

$response->assertCreatedWithServerId(
    'http://localhost/api/v1/posts',
    $data
);

# assertCreatedWithClientId

The assertCreatedWithClientId method asserts that:

  • The response has a 201 Created response status;
  • The response content type is application/vnd.api+json;
  • The response has a Location header set to the resource's URI; AND
  • The data member of the JSON:API document has the expected resource object.

The first argument to the method is the expected Location URI, excluding the client id. The second argument is an array of the expected resource object - which must have the id member set.

For example:

$post = Post::factory()->make();

$data = [
    'type' => 'posts',
    'id' => '7088eccc-a7cd-46f6-81f9-c553c9065dbd',
    'attributes' => [
        'content' => $post->content,
        'slug' => $post->slug,
        'title' => $post->title,
    ],
];

$response = $this
    ->jsonApi()
    ->expects('posts')
    ->withData($data)
    ->post('/api/v1/posts', $data);

$response->assertCreatedWithClientId(
    'http://localhost/api/v1/posts',
    $data
);

# assertCreatedNoContent

The assertCreatedNoContent method asserts that:

  • The response has a 204 No Content response status; AND
  • The response has a Location header set to the resource's URI.

This assertion method receives the expected Location header as its only argument. The expected Location URI must include the expected resource id.

For example:

$post = Post::factory()->make();

$data = [
    'type' => 'posts',
    'id' => '7088eccc-a7cd-46f6-81f9-c553c9065dbd',
    'attributes' => [
        'content' => $post->content,
        'slug' => $post->slug,
        'title' => $post->title,
    ],
];

$response = $this
    ->jsonApi()
    ->expects('posts')
    ->withData($data)
    ->post('/api/v1/posts', $data);

$response->assertCreatedNoContent(
    'http://localhost/api/v1/posts/7088eccc-a7cd-46f6-81f9-c553c9065dbd'
);

WARNING

This assertion only works with resources that allow client ids. This is because a server issued id should result in a response with content that contains a JSON:API document of the resource with its server-issued id.

# Resource Collections

You should use the following assertions when you expect zero-to-many resource objects in the response:

# assertFetchedMany

The assertFetchedMany method asserts that:

  • The response has a 200 OK response status;
  • The response content type is application/vnd.api+json; AND
  • The data member of the JSON:API document is an array matching the expected resources.

TIP

The order of the expected resources is not considered when matching with the actual resources. This makes this assertion useful for tests where you want to check the right resources were returned, but do not care about the order.

If you do care about matching the order, use the assertFetchedManyInOrder assertion.

You can provide either an array representation of the expected resources, or a collection of models.

Providing models just matches the resources based on the expected type and each model's route key.

If providing an array, you can provide a partial representation of each expected resource - i.e. you do not need to provide every attribute, relationship and link. The assertion will just match those you provided.

For example:

use App\Models\Post;

$models = Post::factory()->count(2)->create();

$response = $this
    ->jsonApi()
    ->expects('posts')
    ->get('/api/v1/posts');

$response->assertFetchedMany($models);

// or...

$response->assertFetchedMany([
    [
        'type' => 'posts',
        'id' => (string) $models[0]->getRouteKey(),
        'attributes' => [
            'slug' => $models[0]->slug,
            'title' => $models[0]->title,
        ],
    ],
    [
        'type' => 'posts',
        'id' => (string) $models[1]->getRouteKey(),
        'attributes' => [
            'slug' => $models[1]->slug,
            'title' => $models[1]->title,
        ],
    ],
]);

# assertFetchedManyExact

The assertFetchedManyExact assertion does the same as the assertFetchedMany method, except it does an exact match on each expected resource objects. I.e. it asserts that:

  • The response has a 200 OK response status;
  • The response content type is application/vnd.api+json; AND
  • The data member of the JSON:API document is an array exactly matching the provided resources.

This means you cannot provide models, and your array representation of each expected resource would need to include all attributes, relationships and links.

# assertFetchedManyInOrder

The assertFetchedManyInOrder assertion does the same as the assertFetchedMany method, except that it also asserts that the actual resources appear in the same order as the expected resources. I.e. it asserts that:

  • The response has a 200 OK response status;
  • The response content type is application/vnd.api+json; AND
  • The data member of the JSON:API document is an array matching the expected resources, in the same order.

For example, this is useful when testing the sort query parameter:

use App\Models\Post;

$posts = Post::factory()->count(3)->create();

$response = $this
    ->jsonApi()
    ->expects('posts')
    ->sort('-publishedAt')
    ->get('/api/v1/posts');

$response->assertFetchedManyInOrder($posts->sortByDesc('published_at'));

# assertFetchedNone

The assertFetchedNone method asserts that:

  • The response has a 200 OK response status;
  • The response content type is application/vnd.api+json; AND
  • The data member of the JSON:API document is an empty array.

For example:

use App\Models\Post;

Post::factory()->count(3)->create(['published_at' => null]);

$response = $this
    ->jsonApi()
    ->expects('posts')
    ->filter(['published' => 'true'])
    ->get('/api/v1/posts');

$response->assertFetchedNone();

# Resource Identifiers (Relationships)

You should use the following asserts when you expect resource identifiers in the response - typically for relationship end-points:

# assertFetchedToOne

The assertFetchedToOne method asserts that:

  • The response has a 200 OK response status;
  • The response content type is application/vnd.api+json; AND
  • The data member of the JSON:API document is a resource identifier.

Typically you would use this for a to-one relationship end-point, for example:

use App\Models\Post;

$post = Post::factory()->create();

$response = $this
    ->jsonApi()
    ->expects('users')
    ->get("/api/v1/posts/{$post->getRouteKey()}/relationships/author");

$response->assertFetchedToOne($post->author);

TIP

Note how in this example the expected resource type is users, as that is the resource type returned by the relationship.

# assertFetchedToMany

The assertFetchedToMany method asserts that:

  • The response has a 200 OK response status;
  • The response content type is application/vnd.api+json; AND
  • The data member of the JSON:API document is a collection (array) of resource identifiers.

TIP

The order of the expected resource identifiers is not considered when matching with the actual resource identifiers. This makes this assertion useful for tests where you want to check the right resources were returned, but do not care about the order.

If you do care about matching the order, use the assertFetchedToManyInOrder assertion.

Typically you would use this for a to-many relationship end-point, for example:

use App\Models\Post;
use App\Models\Tag;

$post = Post::factory()->create();
$post->tags()->attach(
  $tags = Tag::factory()->count(3)->create()
);

$response = $this
    ->jsonApi()
    ->expects('tags')
    ->get("/api/v1/posts/{$post->getRouteKey()}/relationships/tags");

$response->assertFetchedToMany($tags);

TIP

Note how in this example the expected resource type is tags, as that is the resource type returned by the relationship.

# assertFetchedToManyInOrder

The assertFetchedToManyInOrder method asserts that:

  • The response has a 200 OK response status;
  • The response content type is application/vnd.api+json; AND
  • The data member of the JSON:API document is a collection (array) of resource identifiers, that has the same order as the expected value.

Typically you would use this for a to-many relationship end-point, combined with the sort query parameter. For example:

use App\Models\Post;
use App\Models\Tag;

$post = Post::factory()->create();
$post->tags()->attach(
  $tags = Tag::factory()->count(3)->create()
);

$response = $this
    ->jsonApi()
    ->expects('tags')
    ->sort('-name')
    ->get("/api/v1/posts/{$post->getRouteKey()}/relationships/tags");

$response->assertFetchedToMany($tags->sortByDesc('name'));

# Included Resources

You should use the following asserts when you expect the top-level included member to contain resources:

# assertIsIncluded

The assertIsIncluded method asserts that the provided model is contained within the top-level included member. This means that the included member might also contain other resources - the assertion will just check for the existence of the provided resource.

Note that because the expected resource type is likely to be different from the resource type you asserted in the data member, you provide the resource type as the first argument to the assertIsIncluded method.

For example, with models:

use App\Models\Post;

$post = Post::factory()->create();

$response = $this
    ->jsonApi()
    ->expects('posts')
    ->includePaths('author')
    ->get('/api/v1/posts' . $post->getRouteKey());

$response->assertFetchedOne($post)
    ->assertIsIncluded('users', $post->author);

# assertIncluded

The assertIncluded method asserts that the included top-level member contains only the provided resources. The order of these resource is not considered because there is no significance to the order of the included resources.

As the included member can contain a mixture of resource types, you must provide the resource type per resource. For example:

use App\Models\Post;
use App\Models\Tag;

$post = Post::factory()->create();
$post->tags()->attach($tag = Tag::factory()->create());

$response = $this
    ->jsonApi()
    ->expects('posts')
    ->includePaths('author', 'tags')
    ->get('/api/v1/posts' . $post->getRouteKey());

$response->assertFetchedOne($post)->assertIncluded([
  ['type' => 'users', 'id' => $post->author],
  ['type' => 'tags', 'id' => $tag],
]);

# assertDoesntHaveIncluded

The assertDoesntHaveIncluded method asserts that the included top-level member is not present in the JSON:API document. For example:

use App\Models\Post;

// post that does not have any tags.
$post = Post::factory()->create();

$response = $this
    ->jsonApi()
    ->expects('posts')
    ->includePaths('tags')
    ->get('/api/v1/posts' . $post->getRouteKey());

$response
    ->assertFetchedOne($post)
    ->assertDoesntHaveIncluded();

# Top-Level Meta

You should use the following assertions when you expect top-level meta in the response:

# assertMetaWithoutData

The assertMetaWihoutData method asserts that:

  • The response has a 200 OK response status;
  • The response content type is application/vnd.api+json; AND
  • The JSON:API document is a meta-only response, i.e. that it has a top-level meta member and the data member does not exist.

This method allows partial matching when checking if the response meta matches the provided expected meta.

For example:

$response->assertMetaWithoutData([
    'foo' => 'bar',
    'baz' => 'bat',
]);

# assertExactMetaWithoutData

The assertExactMetaWihoutData method asserts that:

  • The response has a 200 OK response status;
  • The response content type is application/vnd.api+json; AND
  • The JSON:API document is a meta-only response, i.e. that it has a top-level meta member and the data member does not exist.

This method does an exact match when checking if the response meta matches the provided expected meta.

For example:

$response->assertExactMetaWithoutData([
    'foo' => 'bar',
    'baz' => 'bat',
]);

# assertMeta

The assertMeta method asserts that there is a top-level meta member in the JSON:API document, and it matches the expected meta.

This method allows partial matching when checking if the response meta matches the provided expected meta.

Combine this with our assertFetched* methods to assert top-level meta, for example:

$response->assertFetchedMany($expected)->assertMeta([
    'page' => [
        'number' => 1,
        'size' => 10,
    ],
]);

# assertExactMeta

The assertExactMeta method asserts that there is a top-level meta member in the JSON:API document, and it matches the expected meta.

This method does an exact match when checking if the response meta matches the provided expected meta.

Combine this with our assertFetched* methods to assert top-level meta, for example:

$response->assertFetchedMany($expected)->assertExactMeta([
    'page' => [
        'number' => 1,
        'size' => 10,
    ],
]);

# assertDoesntHaveMeta

The assertDoesntHaveMeta method asserts that the top-level meta member is not present in the JSON:API document. For example:

$response
    ->assertFetchedOne($post)
    ->assertDoesntHaveMeta();

You should use the following assertions when you expect top-level links in the response:

The assertLinks method asserts that there is a top-level links member in the JSON:API document, and it matches the expected links.

This method allows partial matching when checking if the response links matches the provided expected links.

Combine this with our assertFetched* methods to assert top-level links, for example:

$response->assertFetchedOne($expected)->assertLinks([
    'self' => 'http://localhost/api/v1/posts/123/relationships/author',
    'related' => 'http://localhost/api/v1/posts/123/author'
]);

The assertExactLinks method asserts that there is a top-level links member in the JSON:API document, and it matches the expected links.

This method does an exact match when checking if the response links matches the provided expected links.

Combine this with our assertFetched* methods to assert top-level links, for example:

$response->assertFetchedOne($expected)->assertExactLinks([
    'self' => 'http://localhost/api/v1/posts/123/relationships/author',
    'related' => 'http://localhost/api/v1/posts/123/author'
]);

The assertDoesntHaveLinks method asserts that the top-level links member is not present in the JSON:API document. For example:

$response
    ->assertFetchedMany($expected)
    ->assertDoesntHaveLinks();

# Errors

You should use the following assertions when you expect top-level errors in the response:

# assertErrorStatus / assertError

The assertErrorStatus method asserts that:

  • The response has a response status that matches the status member of the expected error object;
  • The response has a content type of application/vnd.api+json; AND
  • The response document has a top-level errors member that contains a single JSON:API error object that matches the expected error object.

This method uses partial matching when checking the expected error object.

In the following example, the assertion checks that the response status is 400, as well as checking that errors member has a single error matching the expected error:

$response->assertErrorStatus([
    'source' => ['parameter' => 'filter'],
    'status' => '400',
    'title' => 'Bad Request',
]);

If you expect the HTTP status code to be different from the status member of the error object, use assertError() instead. In the following example, the assertion checks the HTTP status code is 400 and that the errors member has a single error matching the expected one:

$expected = [
  'source' => ['pointer' => '/data/attributes/title'],
  'status' => '422',
  'title' => 'Invalid Attribute',
];

$response->assertError(400, $expected);

# assertExactErrorStatus / assertExactError

The assertExactErrorStatus method asserts that:

  • The response has a response status that matches the status member of the expected error object;
  • The response has a content type of application/vnd.api+json; AND
  • The response document has a top-level errors member that contains a single JSON:API error object that matches the expected error object.

This method uses exact matching when checking the expected error object.

In the following example, the assertion checks that the response status is 400, as well as checking that errors member has a single error exactly matching the expected error:

$response->assertExactErrorStatus([
    'source' => ['parameter' => 'filter'],
    'status' => '400',
    'title' => 'Bad Request',
]);

If you expect the HTTP status code to be different from the status member of the error object, use assertExactError() instead. In the following example, the assertion checks the HTTP status code is 400 and that the errors member has a single error exactly matching the expected one:

$expected = [
  'source' => ['pointer' => '/data/attributes/title'],
  'status' => '422',
  'title' => 'Invalid Attribute',
];

$response->assertExactError(400, $expected);

# assertErrors

The assertErrors method asserts that:

  • The response has a response status matching the provided expected status;
  • The response has a content type of application/vnd.api+json; and
  • The response document has a top-level errors member that matches the provided expected errors.

This method is useful for asserting that the response document has multiple error objects. You must provide the expected status as the first argument, and the expected error objects as the second argument.

This method allows partial matching when checking the expected errors.

For example:

$response->assertErrors(400, [
    [
        'source' => ['pointer' => '/data/attributes/slug'],
        'status' => '422',
    ],
    [
        'source' => ['parameter' => 'filter'],
        'status' => '400',
    ],
]);

# assertExactErrors

The assertExactErrors method asserts that:

  • The response has a response status matching the provided expected status;
  • The response has a content type of application/vnd.api+json; and
  • The response document has a top-level errors member that matches the provided expected errors.

This method is useful for asserting that the response document has multiple error objects. You must provide the expected status as the first argument, and the expected error objects as the second argument.

This method performs exact matching when checking the expected errors.

For example:

$response->assertErrors(400, [
    [
        'detail' => 'The chosen slug is already taken.',
        'source' => ['pointer' => '/data/attributes/slug'],
        'status' => '422',
        'title' => 'Unprocessable Entity',
    ],
    [
        'detail' => 'The foo filter is not recognised.',
        'source' => ['parameter' => 'filter'],
        'status' => '400',
        'title' => 'Bad Request',
    ],
]);

# assertHasError

The assertHasError method asserts that:

  • The response has a response status matching the provided expected status;
  • The response has a content type of application/vnd.api+json; and
  • The response document has a top-level errors member that contains an error matching the supplied error.

This is useful when a response document contains multiple errors, but for your test you only want to assert that the errors list contains a specific error.

For example:

$response->assertHasError(400, [
    'detail' => 'The chosen slug is already taken.',
    'source' => ['pointer' => '/data/attributes/slug'],
    'status' => '422',
    'title' => 'Unprocessable Entity',
]);

# assertHasExactError

The assertHasExactError method asserts that:

  • The response has a response status matching the provided expected status;
  • The response has a content type of application/vnd.api+json; and
  • The response document has a top-level errors member that contains an error matching the supplied error.

This is useful when a response document contains multiple errors, but for your test you only want to assert that the errors list contains a specific error.

This method performs exact matching when checking the expected error.

For example:

$response->assertHasExactError(400, [
    'detail' => 'The chosen slug is already taken.',
    'source' => ['pointer' => '/data/attributes/slug'],
    'status' => '422',
    'title' => 'Unprocessable Entity',
]);
Last Updated: 2/12/2022, 1:27:17 PM