Back to Course |
Testing in Laravel 9 For Beginners: PHPUnit, Pest, TDD

Testing APIs and JSONs

In the lesson, let's talk about API testing. The testing wouldn't be something like asserting redirect. The API usually contains JSON. 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::middleware('auth:sanctum')->get('/user', function (Request $request) {
return $request->user();
});
 
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.

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()]);
}
 
private function createUser(bool $isAdmin = false): User
{
return User::factory()->create([
'is_admin' => $isAdmin,
]);
}
}

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:

class ProductsTest extends TestCase
{
// ...
 
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);
}
 
private function createUser(bool $isAdmin = false): User
{
return User::factory()->create([
'is_admin' => $isAdmin,
]);
}
}

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:

class ProductsTest extends TestCase
{
// ...
 
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,
]);
}
}

products API tests


Commit for this lesson