0

PHPUnit in Laravel — TestCase, RefreshDatabase, WithFaker

Beginner5 min read·lv-26-001

Concept

HTTP testing in Laravel uses $this->get(), $this->post(), $this->put(), $this->delete(), and friends to make fake HTTP requests through the full application stack without a real browser or web server. Requests go through middleware, routing, controllers, and return real response objects you can assert on.

Test Request Methods:

  • $this->get('/url'): GET request.
  • $this->post('/url', $data): POST with form data.
  • $this->put('/url', $data): PUT.
  • $this->patch('/url', $data): PATCH.
  • $this->delete('/url'): DELETE.
  • $this->json('GET', '/api/url'): Request with Accept: application/json header.
  • $this->getJson('/api/url'): Shorthand for JSON GET.
  • $this->postJson('/api/url', $data): Shorthand for JSON POST.

Response assertions:

  • ->assertStatus(int $code): HTTP status code.
  • ->assertOk(): 200. ->assertCreated(): 201. ->assertNoContent(): 204.
  • ->assertNotFound(): 404. ->assertForbidden(): 403. ->assertUnauthorized(): 401.
  • ->assertRedirect('/url'): Redirect to URL.
  • ->assertJson(array): JSON contains these values.
  • ->assertJsonFragment(array): JSON contains this fragment.
  • ->assertJsonStructure(array): JSON has this key structure.
  • ->assertSee(string): Response body contains text.
  • ->assertSessionHasErrors(array): Session has validation errors.

Code Example

php
<?php
namespace Tests\Feature;

use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
use App\Models\User;
use App\Models\Post;

class PostApiTest extends TestCase
{
    use RefreshDatabase;

    public function test_authenticated_user_can_create_post(): void
    {
        $user = User::factory()->create();

        $response = $this->actingAs($user)->postJson('/api/posts', [
            'title'   => 'My First Post',
            'body'    => 'Post content here.',
            'status'  => 'draft',
        ]);

        $response->assertCreated()
                 ->assertJsonStructure(['data' => ['id', 'title', 'slug', 'status']])
                 ->assertJsonFragment(['title' => 'My First Post', 'status' => 'draft']);

        $this->assertDatabaseHas('posts', ['title' => 'My First Post', 'user_id' => $user->id]);
    }

    public function test_guest_cannot_create_post(): void
    {
        $this->postJson('/api/posts', ['title' => 'Test'])->assertUnauthorized();
    }

    public function test_post_requires_title(): void
    {
        $user = User::factory()->create();
        $response = $this->actingAs($user)->postJson('/api/posts', ['body' => 'No title']);
        $response->assertUnprocessable() // 422
                 ->assertJsonValidationErrors(['title']);
    }

    public function test_published_posts_are_publicly_visible(): void
    {
        $published = Post::factory()->create(['status' => 'published']);
        $draft     = Post::factory()->create(['status' => 'draft']);

        $response = $this->getJson('/api/posts');

        $response->assertOk()
                 ->assertJsonFragment(['id' => $published->id])
                 ->assertJsonMissing(['id' => $draft->id]);
    }

    public function test_post_update_redirects_after_success(): void
    {
        $user = User::factory()->create();
        $post = Post::factory()->for($user)->create();

        $this->actingAs($user)
             ->put("/posts/{$post->id}", ['title' => 'Updated Title', 'body' => 'Body'])
             ->assertRedirect("/posts/{$post->id}");

        $this->assertDatabaseHas('posts', ['id' => $post->id, 'title' => 'Updated Title']);
    }
}