In the Testing For Beginners course, we tested that the response is JSON. However, you might need to test the JSON structure more deeply when creating APIs. Let's see how we can do it.
In this example, we have a route in routes/api.php
and a CRUD Controller. However, the products list is returned using the API Resource, and not all fields are returned deliberately for this example.
routes/api.php:
use App\Http\Controllers\Api\ProductController; Route::get('/user', function (Request $request) { return $request->user();})->middleware('auth:sanctum'); Route::apiResource('products', ProductController::class);
app/Http/Controllers/Api/ProductController.php:
use App\Models\Product;use App\Http\Controllers\Controller;use App\Http\Resources\ProductResource;use Illuminate\Http\Resources\Json\JsonResource; class ProductController extends Controller{ public function index(): JsonResource { return ProductResource::collection(Product::all()); } public function show(Product $product): JsonResource { return ProductResource::make($product); } public function update(Request $request, Product $product): JsonResource { $product->update($request->all()); return ProductResource::make($product); }}
app/Http/Resources/ProductResource.php:
class ProductResource extends JsonResource{ public function toArray(Request $request): array { return [ 'id' => $this->id, 'name' => $this->name, 'price' => $this->price, ]; }}
When we test the /api/products
endpoint using the API client, we will get a list of products from the DB.
Using the assertJsonFragment()
, you can test whether the response contains given data anywhere.
The API Resource returns a partial model, so we can check only a few values if they exist somewhere in the response. Also, using assertJsonFragment()
does not consider that the response is wrapped within the data
key.
Another useful assertion is assertJsonCount()
. When you get a response and know how many records should be inside, assertJsonCount()
can help you test that number.
use App\Models\Product;use function Pest\Laravel\getJson; test('api returns products list', function () { $product1 = `keyProduct::factory()->create(); $product2 = Product::factory()->create(); getJson('/api/products') ->assertJsonFragment([ 'name' => $product1->name, 'price' => $product1->price, ]) ->assertJsonCount(2, 'data');});
Laravel provides a way to test JSON responses fluently. To test this way, you return a closure inside the assertJson()
assertion. This closure will accept an instance of Illuminate\Testing\Fluent\AssertableJson,
which will make assertions inside the closure you use where()
with the exact location of the key to check if the key and value exist and missing()
to see if the key doesn't exist.
Another useful method is etc()
. This method informs Laravel that there might be more JSON objects.
use App\Models\Product;use Illuminate\Testing\Fluent\AssertableJson;use function Pest\Laravel\getJson; test('fluent products list test', function () { $product1 = Product::factory()->create(); $product2 = Product::factory()->create(); getJson('/api/products') ->assertJson(fn (AssertableJson $json) => $json->where('data.0.name', $product1->name) ->where('data.0.price', $product1->price) ->missing('data.0.created_at') ->etc() );});
You might want to test whether a path from the API exists or has the exact structure. For such a task, there are three assertions:
assertJsonPath()
assertJsonMissingPath()
assertJsonStructure()
We can show that in the endpoint of showing the product.
use App\Models\Product;use function Pest\Laravel\getJson; test('api shows products', function () { $product = Product::factory()->create(); getJson('/api/products/' . $product->id) ->assertJsonPath('data.name', $product->name) ->assertJsonMissingPath('data.created_at') ->assertJsonStructure([ 'data' => [ 'id', 'name', 'price', ] ]);});
Or, if you prefer expectations from Pest, you can achieve the same with this syntax:
use App\Models\Product;use function Pest\Laravel\getJson; test('api shows products', function () { $product = Product::factory()->create(); $resp = getJson('/api/products/' . $product->id);; expect($resp->json('data')) ->toHaveKey('name') ->not->toHaveKey('created_at') ->toHaveKeys(['id', 'name', 'price']) ->name->toEqual($product->name);});
A typical example could be when a record is updated to check if the original value is not returned.
test('api updates product', function () { $product = Product::factory()->create(); putJson('/api/products/' . $product->id, [ 'name' => 'new name', 'price' => 100, ]) ->assertJsonMissing(['name' => $product->name, 'price' => $product->price]);});
Or, again, if you prefer the expectations from Pest:
use App\Models\Product;use function Pest\Laravel\putJson; test('api updates product', function () { $product = Product::factory()->create(); $resp = putJson('/api/products/' . $product->id, [ 'name' => 'new name', 'price' => 100, ]); expect($resp->json('data')) ->name->not->toBe($product->name) ->price->not->toBe($product->price);});
Of course, there are more JSON assertions, which you can find in the Laravel documentation.