Skip to Content
Course content

167: Testing HTTP Endpoints in Laravel

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I’ve been working on this Order Status endpoint for a client. The logic is simple: you send an order ID, and it tells you if the package is 'processing', 'shipped', or 'delivered'. Now, I could just keep hitting the endpoint with Postman, but as soon as I touch the database logic, I'm going to break something. Let's automate this.

The "It works on my machine" trap

I'll start by writing a basic test. I have my route set up at /api/orders/{id}/status. My first instinct is to just hit that URL and see if I get a 200 OK response. Here is my first attempt at a test method:

public function test_order_status_endpoint_returns_success()
{
    $response = $this->getJson('/api/orders/1/status');

    $response->assertStatus(200);
}

I run php artisan test and... it fails. 404 Not Found. Now, if I check my browser or Postman, it works fine. Why? Because my local development database has an order with ID 1, but the testing environment uses a separate, clean SQLite database in memory. It's empty. I'm testing a ghost.

Wait, where did the data go?

I can't hardcode IDs in tests; that's a recipe for disaster. I need to actually create the data I'm testing against within the test itself. Since we've already set up Factories in earlier lessons, I'll use one to spin up a real order on the fly.

public function test_order_status_endpoint_returns_success()
{
    // Create a real order in the test database
    $order = Order::factory()->create(['status' => 'shipped']);

    // Use the ID from the created model
    $response = $this->getJson("/api/orders/{$order->id}/status");

    $response->assertStatus(200);
}

That passes. But here is the thing: a 200 status code only tells me the server didn't crash. It doesn't tell me if the API actually returned the correct status. It could be returning a blank page and still be a "success" in the eyes of the HTTP protocol. I need to peek inside the JSON payload.

Checking the payload

I want to make sure the JSON contains the key status and that its value matches what I put in the factory. Laravel gives us assertJson for this, which is a lifesaver. I'll tweak the test to be more specific.

public function test_order_status_returns_correct_data()
{
    $order = Order::factory()->create(['status' => 'processing']);

    $response = $this->getJson("/api/orders/{$order->id}/status");

    $response->assertStatus(200)
             ->assertJson([
                 'status' => 'processing',
             ]);
}

Now we're talking. If I accidentally change the controller to return 'pending' instead of 'processing', this test will catch it. It's no longer just checking if the door is open; it's checking if the right person is standing behind the door.

Handling the missing pieces

Finally, I have to think about the "sad path." What happens if a user requests an order ID that doesn't exist? If I haven't handled this in my controller, Laravel might throw a generic 500 error or a messy HTML exception page. For an API, that's unacceptable—we need a clean 404 JSON response.

I'll write a test for the failure state first. This is basically Test-Driven Development (TDD) at this point.

public function test_order_status_returns_404_for_invalid_order()
{
    // We don't create any order here
    $response = $this->getJson('/api/orders/99999/status');

    $response->assertStatus(404);
}

This fails with a 500 error because my controller is currently just calling Order::findOrFail($id) without a try-catch block or a proper handler for the API. I'll go back to the controller, wrap that logic, or let the Laravel exception handler take over by ensuring the request header is Accept: application/json (which getJson does automatically). Once I've fixed the controller to return a proper 404, this test turns green.

By testing both the "Happy Path" and the "Sad Path," I can now refactor my controller logic with total confidence. If I break the 404 handling while trying to optimize the 200 response, the tests will scream at me immediately.




📋 Practical Task

Exercise: Validating the Product Inventory API

You have been handed a Laravel project with a ProductController that has an endpoint: GET /api/products/{id}/inventory. This endpoint should return the current stock level of a product.

Your Goal: Write a test class ProductInventoryTest that implements the following three scenarios:

  • The Success Case: Create a Product using a factory with a specific stock_count (e.g., 15). Assert that the endpoint returns a 200 status and a JSON response containing {"stock": 15}.
  • The Empty Case: Create a Product with a stock_count of 0. Assert that the endpoint still returns a 200 status, but the JSON reflects the 0 stock.
  • The Missing Product Case: Request an ID that does not exist in the database. Assert that the endpoint returns a 404 status.

Tip: Ensure you are using $this->getJson() to simulate the API request and that you are creating your models inside the test methods to avoid database dependency issues.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.