Who this is for: Laravel beginners who can read routes and controllers and want to check application behavior with automated tests.
What you will learn: how a feature test sends a request through Laravel, how to assert responses and validation errors, how to fake outgoing mail, and how to isolate database tests.
Prerequisites: Basic PHP and familiarity with Laravel routes and forms. The examples below are based on the tests in this project.
Tested environment: Laravel Framework 13.16.1 and PHP 8.4.15. PHPUnit is configured to use SQLite in memory, the array mailer, and a synchronous queue for tests. These settings are defined in phpunit.xml.
What does a feature test check?
A feature test makes a request through Laravel's application and checks the observable result: a successful page response, a redirect, a validation error, a rendered value, or a mail action. Unlike a unit test of one isolated method, it can cover several application layers together without running a production web server.
This project keeps these checks in tests/Feature/. Run them with:
php artisan test
Check a form's response and validation errors
TeachyLeaf's contact tests submit data to the real contact.store route. A valid submission is checked for its redirect and mail action; a separate test submits invalid values and checks that validation errors are present.
$response = $this->from('/contact')->post(route('contact.store'), [
'name' => 'Amit Kumar Das',
'email' => 'amit@example.com',
'subject' => 'Tutorial correction',
'message' => 'Please review this example.',
]);
$response->assertRedirect(route('contact'));
$invalidResponse = $this->from('/contact')->post(route('contact.store'), [
'name' => 'A',
'email' => 'not-an-email',
'subject' => 'X',
'message' => 'short',
]);
$invalidResponse->assertSessionHasErrors(['name', 'email', 'subject', 'message']);
The test does not send its invalid example to the live site. It runs inside the local Laravel test process and verifies the validation response that the application creates.
Fake external mail in automated tests
Automated tests should not send real messages. Mail::fake() replaces delivery with a test double, allowing the test to check that the application attempted to send the expected mailable:
Mail::fake();
$response = $this->post(route('contact.store'), [
'name' => 'Amit Kumar Das',
'email' => 'amit@example.com',
'subject' => 'Tutorial correction',
'message' => 'Please review this example.',
]);
$response->assertRedirect(route('contact'));
Mail::assertSent(ContactMessage::class);
Other contact tests verify that invalid input produces session errors, a filled honeypot does not send mail, and a simulated transport exception returns an error message. These are deterministic checks of application behavior; the fake mailer does not connect to SMTP or prove that an email arrives.
That is why automated and live checks serve different purposes. On 2026-11-09, the site owner manually confirmed that a production contact-form submission reached the intended inbox and the form worked as expected.
Use isolated database tests for blog behavior
The Eloquent lesson tests use Laravel's RefreshDatabase trait and Blog factories. The trait prepares a clean schema for the test; factories create records with controlled status, slugs, and dates so the test can check ordering and draft visibility.
use Illuminate\Foundation\Testing\RefreshDatabase;
class BlogPageTest extends TestCase
{
use RefreshDatabase;
public function test_blog_hides_drafts(): void
{
Blog::factory()->create([
'title' => 'Visible published post',
'slug' => 'visible-published-post',
'status' => 'Published',
]);
Blog::factory()->create([
'title' => 'Private draft post',
'slug' => 'private-draft-post',
'status' => 'Draft',
]);
$response = $this->get('/blog');
$response->assertOk();
$response->assertSee('Visible published post');
$response->assertDontSee('Private draft post');
}
}
Factories make test records explicit and repeatable. Assertions should check behavior that matters to a visitor rather than implementation details that may change without changing the result.
Know what the test environment does not prove
- An in-memory SQLite test does not prove a schema change behaves identically on a production database engine.
- A mail fake checks the application's intent to send, not DNS, SMTP credentials, delivery, spam filtering, or inbox placement.
- An HTTP feature test does not by itself check the appearance of a page in every browser or at every screen size.
- Passing tests cover only the cases they exercise; review edge cases and test failures rather than treating a green suite as a guarantee.
Use tests for fast, repeatable feedback, then use environment-appropriate staging, browser, and production checks for behavior the test configuration cannot observe.
Try it in this project
- Read
tests/Feature/ContactPageTest.phpand find the valid-submission, invalid-input, honeypot, and mail-failure tests. - Read
tests/Feature/LaravelEloquentBlogQueriesLessonTest.phpand identify where factories create published and draft posts. - Inspect the test environment in
phpunit.xml, especiallyDB_DATABASEandMAIL_MAILER. - Run
php artisan test --compactand review any failing test output before changing code.