<?php
namespace Tests\Feature;
use App\Models\Post;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class PostTest extends TestCase
{
use RefreshDatabase;
public function test_user_can_view_posts()
{
$posts = Post::factory()->count(3)->create();
$response = $this->get('/posts');
$response->assertStatus(200);
$response->assertSee($posts[0]->title);
}
public function test_authenticated_user_can_create_post()
{
$user = User::factory()->create();
$response = $this->actingAs($user)->post('/posts', [
'title' => 'Test Post',
'body' => 'This is a test post body that is long enough.',
]);
$response->assertRedirect();
$this->assertDatabaseHas('posts', [
'title' => 'Test Post',
'user_id' => $user->id,
]);
}
public function test_guest_cannot_create_post()
{
$response = $this->post('/posts', [
'title' => 'Test Post',
'body' => 'Test body',
]);
$response->assertRedirect('/login');
}
public function test_post_requires_title()
{
$user = User::factory()->create();
$response = $this->actingAs($user)->post('/posts', [
'body' => 'Test body',
]);
$response->assertSessionHasErrors('title');
}
public function test_user_can_only_delete_own_posts()
{
$user = User::factory()->create();
$otherUser = User::factory()->create();
$post = Post::factory()->create(['user_id' => $otherUser->id]);
$response = $this->actingAs($user)->delete("/posts/{$post->id}");
$response->assertForbidden();
$this->assertDatabaseHas('posts', ['id' => $post->id]);
}
}
<?php
namespace Tests\Unit;
use App\Models\Post;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class PostTest extends TestCase
{
use RefreshDatabase;
public function test_post_belongs_to_user()
{
$post = Post::factory()->create();
$this->assertInstanceOf(User::class, $post->author);
}
public function test_post_has_read_time()
{
$post = Post::factory()->create([
'body' => str_repeat('word ', 200), // 200 words
]);
$this->assertEquals(1, $post->readTime()); // 1 minute
}
public function test_published_scope_only_returns_published_posts()
{
Post::factory()->create(['published_at' => now()]);
Post::factory()->create(['published_at' => null]);
$published = Post::published()->get();
$this->assertCount(1, $published);
}
}
Laravel's testing suite built on PHPUnit makes writing tests straightforward. Feature tests simulate HTTP requests and assert responses, while unit tests focus on individual methods. I use database transactions or RefreshDatabase to reset the database after each test. Factories generate test data consistently. The actingAs() method authenticates users for protected routes. Assertions like assertStatus(), assertSee(), assertDatabaseHas() verify behavior. I mock external services with facades or the Mock class. Test organization follows the AAA pattern—Arrange, Act, Assert. Running php artisan test executes the suite with pretty output. Comprehensive tests catch regressions and enable confident refactoring.