Laravel RefreshDatabase vs DatabaseTransactions is one of the most common sources of confusion when writing tests in Laravel.
Choosing the wrong approach can lead to flaky tests, hidden bugs, and unreliable results.
When writing tests in Laravel, database state can quickly become a source of confusion.
One test passes, another fails. Data seems to “leak” between tests. Records appear when they shouldn’t — or disappear when you expect them to exist.
If you’ve experienced this, you’re not alone.
In most cases, the issue comes down to how your tests handle the database. Laravel provides two primary approaches for this:
RefreshDatabaseDatabaseTransactions
At first glance, they seem similar. In reality, they behave very differently — and choosing the wrong one can lead to subtle bugs, flaky tests, or even false confidence in your test suite.
In this guide, you’ll learn exactly how both approaches work, their trade-offs, and when to use each in real-world scenarios.
Last article in this category: https://codecraftdiary.com/2026/03/08/laravel-queue-testing-jobs-retries/
PHP Unit docs: https://phpunit.de/documentation.html
Understanding the Core Problem
Before diving into Laravel specifics, let’s clarify the core issue:
Tests must be isolated.
Each test should run independently, without being affected by previous tests.
If your database is not reset properly between tests:
- data can persist unexpectedly
- test results become unreliable
- debugging becomes painful
Laravel solves this problem using two strategies:
- Reset the database completely
- Wrap each test in a transaction and roll it back
Article about practice feature testing in PHP: https://codecraftdiary.com/2025/10/30/feature-testing-in-php-ensuring-the-whole-system-works-together/
Option 1: RefreshDatabase
The RefreshDatabase trait is the most commonly used solution.
use Illuminate\Foundation\Testing\RefreshDatabase;
class UserTest extends TestCase
{
use RefreshDatabase;
}
PHPHow it works
- Runs migrations before tests
- Ensures a clean database state
- Uses transactions internally when possible
What this means in practice
Every test starts with:
- a fresh schema
- no leftover data
- consistent environment
Advantages
1. High reliability
Each test runs in a completely clean environment.
2. Works with everything
- queues
- jobs
- events
- external processes
3. Predictable behavior
No surprises caused by hidden state.
Disadvantages
1. Slower
Running migrations repeatedly can add overhead.
2. Heavier setup
Especially noticeable in large applications.
Option 2: DatabaseTransactions
The DatabaseTransactions trait takes a different approach.
use Illuminate\Foundation\Testing\DatabaseTransactions;
class UserTest extends TestCase
{
use DatabaseTransactions;
}
PHPHow it works
- Starts a database transaction before each test
- Rolls it back after the test finishes
What this means
Instead of resetting the database:
- changes are never committed
- everything is undone automatically
Advantages
1. Fast
Transactions are significantly quicker than migrations.
2. Lightweight
No need to rebuild the database schema.
Disadvantages
1. Limited scope
Does NOT work well with:
- queued jobs
- async processes
- separate DB connections
2. Hidden pitfalls
If something runs outside the transaction:
- it won’t be rolled back
- tests may behave inconsistently
Key Differences
Here’s a clear comparison:
| Feature | RefreshDatabase | DatabaseTransactions |
|---|---|---|
| Speed | Slower | Faster |
| Isolation | High | Medium |
| Works with queues | Yes | No |
| Works across connections | Yes | No |
| Setup complexity | Medium | Low |
When to Use RefreshDatabase
Use RefreshDatabase when:
1. You are testing queues or jobs
Jobs often run outside the main transaction.
dispatch(new ProcessOrderJob($order));
PHPIf you use DatabaseTransactions, the job may not see the data at all.
2. You rely on multiple database connections
Transactions don’t span across connections reliably.
3. You want maximum reliability
If your priority is correctness over speed, this is the safer option.
When to Use DatabaseTransactions
Use DatabaseTransactions when:
1. You are testing simple database interactions
Example:
- repository logic
- basic CRUD operations
2. Performance matters
In large test suites, this can significantly reduce execution time.
3. Everything runs in a single request lifecycle
No queues, no async behavior, no external processes.
Common Mistakes (and How to Avoid Them)
Mixing both traits
use RefreshDatabase, DatabaseTransactions;
PHPThis creates unpredictable behavior.
Fix: Choose one strategy per test class.
Using transactions with queues
Queue::fake();
PHPEven with fakes, underlying behavior may break if data isn’t committed.
Fix: Use RefreshDatabase for queue-related tests.
Assuming transactions isolate everything
They don’t.
Anything outside the transaction:
- won’t be rolled back
- can pollute your test environment
Ignoring test failures caused by state
Flaky tests often indicate:
improper database isolation
Don’t ignore them — fix the root cause.
Real-World Example
Let’s say you’re testing order processing.
public function test_order_is_processed()
{
$order = Order::factory()->create();
dispatch(new ProcessOrderJob($order));
$this->assertDatabaseHas('orders', [
'id' => $order->id,
'status' => 'processed',
]);
}
PHPWith DatabaseTransactions
- job may not see the order
- test fails unexpectedly
With RefreshDatabase
- data is committed
- job works correctly
- test passes
Recommended Strategy
For most Laravel applications, the safest default is:
use RefreshDatabase;
PHPThen optimize later if needed.
Hybrid approach (advanced)
You can:
- use
RefreshDatabasefor feature tests - use
DatabaseTransactionsfor isolated unit-like tests
This gives you:
- reliability where needed
- speed where possible
Final Thoughts
Database testing in Laravel isn’t just about writing assertions — it’s about controlling state.
Choosing between RefreshDatabase and DatabaseTransactions directly affects:
- test reliability
- debugging time
- overall confidence in your application
If you’re unsure which one to use, start with:
RefreshDatabase
It may be slightly slower, but it will save you hours of debugging and prevent subtle, hard-to-track issues.
Once your test suite grows, you can selectively introduce DatabaseTransactions where performance matters and constraints are well understood.
TL;DR
- Use RefreshDatabase for reliability and real-world scenarios
- Use DatabaseTransactions for speed in simple cases
- Avoid mixing both
- Be mindful of queues and async behavior
A well-structured test suite isn’t just about coverage — it’s about trust.
And that trust starts with a clean, predictable database.

