Test Public Articles and Drafts with Laravel Feature Tests
Verify that published articles are readable while draft detail URLs remain inaccessible.
You are reading a translated version.
Hiding drafts from a listing is insufficient. A visitor might know a detail URL and open it directly. A feature test should exercise that request.
This example assumes /articles/{post:slug}, a Post factory, and a published_at column. Adapt the path to your application. Use a separate test database: never run RefreshDatabase against production.
Implementation example
// Public controller action:
public function show(\App\Models\Post $post)
{
abort_unless(
$post->published_at !== null && $post->published_at->lte(now()),
404
);
return view('articles.show', compact('post'));
}
namespace Tests\Feature;
use App\Models\Post;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class ArticleVisibilityTest extends TestCase
{
use RefreshDatabase;
public function test_only_published_articles_are_public(): void
{
$published = Post::factory()->create(['published_at' => now()->subMinute()]);
$draft = Post::factory()->create(['published_at' => null]);
$scheduled = Post::factory()->create(['published_at' => now()->addDay()]);
$this->get('/articles/'.$published->slug)->assertOk();
$this->get('/articles/'.$draft->slug)->assertNotFound();
$this->get('/articles/'.$scheduled->slug)->assertNotFound();
}
}
Check the result
Add a datetime cast for published_at, then run php artisan test --filter=ArticleVisibilityTest. The published article should return HTTP 200; drafts and future publications should return 404.
The factory must populate required columns and generate unique slugs. Isolate jobs or external integrations triggered when models are saved. As an exercise, test the listing page too, verifying drafts are absent. Detail and listing tests protect different access paths.
