Back to Course |
How to Build Laravel 11 API From Scratch

Testing APIs and JSONs

Testing is crucial in every application, especially if that application needs to be maintained. So, in this lesson, let's see how we can test APIs.


Before creating a test for the API, let's quickly set up our application.

It is crucial to run tests on a separate database from your main one, because it wipes and re-creates the database with every test. The easiest way is to uncomment two lines in the phpunit.xml file, then your tests will run on an in-memory SQLite database.

phpunit.xml:

<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php"
colors="true"
>
<testsuites>
<testsuite name="Unit">
<directory>tests/Unit</directory>
</testsuite>
<testsuite name="Feature">
<directory>tests/Feature</directory>
</testsuite>
</testsuites>
<source>
<include>
<directory>app</directory>
</include>
</source>
<php>
<env name="APP_ENV" value="testing"/>
<env name="APP_MAINTENANCE_DRIVER" value="file"/>
<env name="BCRYPT_ROUNDS" value="4"/>
<env name="CACHE_STORE" value="array"/>
<!-- <env name="DB_CONNECTION" value="sqlite"/> -->
<!-- <env name="DB_DATABASE" value=":memory:"/> -->
<env name="MAIL_MAILER" value="array"/>
<env name="PULSE_ENABLED" value="false"/>
<env name="QUEUE_CONNECTION" value="sync"/>
<env name="SESSION_DRIVER" value="array"/>
<env name="TELESCOPE_ENABLED" value="false"/>
</php>
</phpunit>

Next, you must know that every test class must have Illuminate\Foundation\Testing\RefreshDatabase trait to create tables. Without this trait, you will get the error message no such table:.


Now, let's create a test class.

php artisan make:test Api/CategoryTest

Similar to Controllers, we create tests inside an Api directory. The first test will be to get a list of categories.

tests/Feature/Api/CategoryTest.php:

use Tests\TestCase;
use App\Models\User;
use App\Models\Category;
use Illuminate\Support\Arr;
use Illuminate\Foundation\Testing\RefreshDatabase;
 
class CategoryTest extends TestCase
{
use RefreshDatabase;
 
public function test_api_returns_categories_list(): void
{
$user = User::factory()->create();
$category = Category::factory()->create();
 
$response = $this->actingAs($user)->getJson(route('categories.index'));
 
$response->assertStatus(200)
->assertJsonCount(1, 'data')
->assertJson([
'data' => [Arr::only($category->toArray(), ['id', 'name'])]
]);
}
}

First, we create a user and category using Factories. Then, using that user, we get the Route. Because we used API Resource Route, Laravel automatically assigns names to Routes, and we can use those names in the tests.

And, finally, we must assert something. The first status assertion is a basic check of the returned HTTP status code. Next, we check that we have one value under the data key. In this case, we have one because we created one category.

For the last assertion, we check that the data is correct. The assertJson accepts an array. That array must be as the data we get from an API. So the first key is data with id and name from the $category. Because we used API Resource to show only the id and name, we need to specify only these two keys in the test.

Also, did you see that two assertions and get method have json in their name? All assertions used for JSON have a json in their name. So, for example, instead of post, we would use postJson.


For the second test, let's create a test for creating a category.

tests/Feature/Api/CategoryTest.php:

class CategoryTest extends TestCase
{
// ...
 
public function test_api_category_store_successful()
{
$category = ['id' => 1, 'name' => 'test category', 'description' => 'test'];
$user = User::factory()->create();
 
$response = $this->actingAs($user)->postJson(route('categories.store'), $category);
 
$response->assertStatus(201)
->assertJson([
'data' => Arr::only($category, ['id', 'name'])]
);
}
}

This test is similar to the first one. We create a user and, acting as this user, post to a store endpoint with the category data.

This time, we assert that the HTTP status is 201. With the assertJson, we don't need to wrap category data in its array because this is a single model instead of a Collection we had earlier.

Let's remember to run the tests. We can do this via the artisan command php artisan test.


For more about testing, check the course Testing in Laravel 11 For Beginners.