Courses

Testing in Laravel 12 For Beginners

Testing APIs and JSON Data

Summary of this lesson:
- Testing JSON API endpoints
- Using getJson and postJson methods
- Verifying API responses and status codes
- Testing API validation errors

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

Since Laravel 11 with a fresh Laravel project there is no routes/api.php file. To add it we must use an Artisan command and follow its output.

php artisan install:api

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('auth: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,
]);
}
}
Previous: Delete Product Testing
avatar
Luis Antonio Parrado

routes/api.php doesn't exist in Laravel 11 by default, If a user is following this course and is starting out in Laravel 11 we should instruct him to first run the command php artisan install:api.

👍 6
avatar
You can use Markdown
avatar

Just to follow up on Luis' comment. If you get an error when running install:api

Unable to automatically add API route definition to bootstrap file. API route file should be registered manually.

Go to your bootstrap/app.php file and add the following:

->withRouting(
        web: __DIR__ . '/../routes/web.php',
        api: __DIR__ . '/../routes/api.php',
        commands: __DIR__ . '/../routes/console.php',
        health: '/up',
        apiPrefix: '/api',
    )
👍 1
avatar
You can use Markdown
avatar
You can use Markdown