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.
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.
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, ]); }}