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 withAccept: application/jsonheader.$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']);
}
}