Skip to content

Chapter 2.8: API Testing

Time to learn: 1 hour


1. Why Are Tests Necessary?

Imagine you've built a spaceship. Before sending it to Mars, you would conduct thousands of checks on Earth. Tests in programming are the same thing. They:

  • Give Confidence: You can change your code, and if the tests pass, it means you haven't broken anything.
  • Save Time: Instead of manually "clicking through" everything in Postman after every change, you run one command, and it checks everything for you in seconds.
  • Serve as Documentation: Good tests show how your API is supposed to work.

2. Setting Up the Test "Laboratory"

Laravel makes setting up tests incredibly simple. By default, it uses a separate configuration so as not to affect your main database.

Database for Tests: By default, Laravel uses an in-memory database (:memory:). This is the fastest way because nothing needs to be written to disk. The database is created before the tests run and destroyed after they finish. We don't even need to configure anything for this!

Creating a Test File: Let's create a special file for tests related to planets.

php artisan make:test PlanetApiTest

This command will create the file tests/Feature/PlanetApiTest.php. The word Feature means we will be testing functionality as a whole (e.g., "can a user create a planet?"), not a single small class.


3. Anatomy of a Test: Arrange, Act, Assert

Open tests/Feature/PlanetApiTest.php. Inside, we will write our first test. A good test always consists of three parts (Arrange, Act, Assert).

<?php

namespace Tests\Feature;

use App\Models\Planet; // Don't forget to import the model
use Illuminate\Foundation\Testing\RefreshDatabase; // A crucial tool!
use Tests\TestCase;

class PlanetApiTest extends TestCase
{
    // This trait "magically" clears and recreates
    // our test database before each test.
    // This ensures that tests do not affect each other.
    use RefreshDatabase;

    /**
     * Test: the endpoint for getting a list of planets works correctly.
     * Test names should be meaningful!
     */
    public function test_can_get_all_planets(): void
    {
        // 1. ARRANGE
        // Create 3 fake planets in our test database
        // using the factory we created earlier.
        Planet::factory()->count(3)->create();

        // 2. ACT
        // Simulate a real GET request to our API.
        $response = $this->getJson('/api/planets');

        // 3. ASSERT
        // Check that everything went as expected.
        $response->assertStatus(200); // Expect the server to respond with "200 OK"
        $response->assertJsonCount(3, 'data'); // Expect exactly 3 planets in the response data
    }
}
Key Points:

  • use RefreshDatabase: This trait is your best friend. It ensures that each test starts with a "clean slate," with an empty database.
  • Planet::factory(): Factories are ideal for creating test data.
  • $this->getJson(): This is a special Laravel method for sending API requests within tests.
  • assert...(): These are "assertions" or "checks." If even one of them fails, the test will fail.

4. Testing Basic Operations (CRUD)

Let's write tests for creating, updating, and deleting planets.

A. Test for creating a planet (POST)

<?php
public function test_can_create_a_planet(): void
{
    // 1. Arrange: prepare the data for the new planet
    $planetData = [
        'name' => 'Kepler-186f',
        'description' => 'The first Earth-sized exoplanet in a habitable zone.',
        'size_km' => 14000,
        'solar_system' => 'Kepler-186',
        'is_habitable' => true
    ];

    // 2. Act: send a POST request with the data
    $response = $this->postJson('/api/planets', $planetData);

    // 3. Assert
    $response->assertStatus(201); // Expect a "201 Created" status
    $response->assertJsonFragment(['name' => 'Kepler-186f']); // Check that the response contains the created name

    // The most important check: did the data actually get into the database?
    $this->assertDatabaseHas('planets', [
        'name' => 'Kepler-186f'
    ]);
}

B. Test for deleting a planet (DELETE)

<?php
public function test_can_delete_a_planet(): void
{
    // 1. Arrange: create a planet to be deleted
    $planet = Planet::factory()->create();

    // 2. Act: send a DELETE request
    $response = $this->deleteJson("/api/planets/{$planet->id}");

    // 3. Assert
    $response->assertStatus(204); // Expect "204 No Content" - successful deletion

    // Check that the record has actually disappeared from the database
    $this->assertDatabaseMissing('planets', [
        'id' => $planet->id
    ]);
}


5. Testing "Bad" Scenarios

Testing success cases is good. But testing for errors is even more important!

A. Test for a validation error

<?php
public function test_creation_fails_with_invalid_data(): void
{
    // 2. Act: send deliberately incorrect data
    $response = $this->postJson('/api/planets', ['name' => '']); // Empty name

    // 3. Assert
    $response->assertStatus(422); // Expect "422 Unprocessable Entity"
    $response->assertJsonValidationErrors('name'); // Expect the error to be specifically in the 'name' field
}

B. Test for "not found" (404)

<?php
public function test_returns_404_for_non_existent_planet(): void
{
    // 2. Act: request a planet with a non-existent ID
    $response = $this->getJson('/api/planets/99999');

    // 3. Assert
    $response->assertStatus(404); // Expect "404 Not Found"
}


6. Running Tests

Now that the tests are written, running them is very simple. Execute in the terminal:

php artisan test

Laravel will find all your tests and run them one by one. If everything is successful, you will see green output. If a test fails, you will see red output with a detailed error description, allowing you to fix it quickly.

To run only one specific file:

php artisan test tests/Feature/PlanetApiTest.php


7. Code Coverage

Step 1: Install Xdebug

To collect code coverage information, the PHP extension Xdebug is required.

Submit your php -i output to the wizard and follow the instructions.

Step 2: Configure phpunit.xml

<phpunit ... >
    <coverage>
        <include>
            <directory suffix=".php">./app</directory>
        </include>
    </coverage>
</phpunit>

Step 3: Run with a report

php artisan test --coverage-html=coverage
Report: Open coverage/index.html in your browser.


8. Integration with Postman

Automation via Newman:

  1. Export your Postman collection to tests/Postman/SpaceApi.postman_collection.json
  2. Install Newman:
    npm install -g newman
    
  3. Add a script to composer.json:
    "scripts": {
        "test:postman": "newman run tests/Postman/SpaceApi.postman_collection.json"
    }
    
  4. Run:
    composer test:postman
    

Review Quiz

1. The command to create a test class is:

2. The `RefreshDatabase` trait is used for:

3. The method for checking a JSON structure is:

4. Factories in Laravel are needed for:

5. The tool for running Postman collections from the CLI is:


🚀 Chapter Summary:

You have conducted a full cycle of pre-flight checks! Now your API skills include:

  • ✅ Setting up a test environment effortlessly.
  • 🛡️ Writing tests using the "Arrange-Act-Assert" principle.
  • 📊 Testing both success scenarios (CRUD) and errors (validation, 404).
  • 🔁 Running tests with a single command and being confident in your code.

The spaceship is ready for launch! You have completed the section on creating an API in Laravel.

📌 Final Check:

  1. Run php artisan test
  2. Make sure all tests pass (green light!)
  3. Check the coverage report

⚠️ If tests are failing:

  • Check the API's functionality via Postman
  • Ensure the test database is configured
  • Use dd($response->content()) for debugging

Congratulations on completing Chapter 2! You have not just created an API, you have built a reliable and tested "spaceship," ready for further missions.

🌌 Next Steps:

  1. Setting up authentication (Sanctum)
  2. Documenting the API with Swagger
  3. Deploying to a server (Forge, VPS)
  4. Writing a frontend in Vue/React

Good luck with your space mission! In the next chapter, we will look at writing an API from scratch 🚀