Back to Course |
Testing in Laravel 11 For Beginners

Testing APIs and JSON Data

In this lesson, let's talk about API testing. In this case, we don't have any redirects to assert, and APIs usually work with JSON. So, we will see a couple of examples.


Laravel Code

In this example, we have a route in the route/api.php and two Controller methods to list products and store a product.

routes/api.php:

Route::get('/user', function (Request $request) {
return $request->user();
})->middleware(Authenticate::using('sanctum'));
 
Route::apiResource('products', \App\Http\Controllers\Api\ProductController::class);
php artisan make:controller Api/ProductController --resource --api --model=Product

app/Http/Controllers/Api/ProductController.php:

use App\Models\Product;
use App\Http\Requests\StoreProductRequest;
 
class ProductController extends Controller
{
public function index()
{
return Product::all();
}
 
public function store(StoreProductRequest $request)
{
return Product::create($request->validated());
}
}

If we test /api/products using any API client, we will get a list of products from the DB.

api products list


The Tests

We will continue to add tests in the same ProductsTest file. You can create a tests/Feature/Api directory, for example, and add all your API tests there.

In the first test, to get all products list, we will create a product using Factory and make a GET request. But the critical part is instead of a get to, use getJson. And we can assertJson that the product is returned. The assertJson accepts an array as a parameter. Using Pest expectations we can also has expectation for json().

tests/Feature/ProductsTest.php:

// ...
 
test('api returns products list', function () {
$product = Product::factory()->create();
 
$res = getJson('/api/products')
->assertJson([$product->toArray()]);
 
expect($res->content())
->json()
->toHaveCount(1);
});

Now, let's add a validation test. And for testing APIs, the HTTP status codes are very important. First, we will add a test for successfully storing a product.

Similar to a GET request, here we must use postJson to send a POST request. The assertions would, first, be the status code 201, which means created, and second, the returned record is the one we created.

tests/Feature/ProductsTest.php:

use function Pest\Laravel\postJson;
 
// ...
 
test('api product store successful', function () {
$product = [
'name' => 'Product 1',
'price' => 123
];
 
postJson('/api/products', $product)
->assertStatus(201)
->assertJson($product);
});

Now, let's add a test for the unsuccessful creation of a product. In the test, we will not pass the product name, which means the validation will fail. And the expected request status now will be 422, which means Unprocessable Entity.

tests/Feature/Products/Test.php:

// ...
 
test('api product invalid store returns error', function () {
$product = [
'name' => '',
'price' => 123
];
 
postJson('/api/products', $product)
->assertStatus(422);
});

products API tests


PHPUnit examples

tests/Feature/ProductsTest.php:

class ProductsTest extends TestCase
{
// ...
 
public function test_api_returns_products_list()
{
$product = Product::factory()->create();
$response = $this->getJson('/api/products');
 
$response->assertJson([$product->toArray()]);
}
 
public function test_api_product_store_successful()
{
$product = [
'name' => 'Product 1',
'price' => 123
];
$response = $this->postJson('/api/products', $product);
 
$response->assertStatus(201);
$response->assertJson($product);
}
 
public function test_api_product_invalid_store_returns_error()
{
$product = [
'name' => '',
'price' => 123
];
$response = $this->postJson('/api/products', $product);
 
$response->assertStatus(422);
}
 
private function createUser(bool $isAdmin = false): User
{
return User::factory()->create([
'is_admin' => $isAdmin,
]);
}
}