Laravel RefreshDatabase vs DatabaseTransactions: When to Use Each

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:

  • RefreshDatabase
  • DatabaseTransactions

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


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:

  1. Reset the database completely
  2. 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/


The RefreshDatabase trait is the most commonly used solution.

use Illuminate\Foundation\Testing\RefreshDatabase;

class UserTest extends TestCase
{
    use RefreshDatabase;
}
PHP
  • Runs migrations before tests
  • Ensures a clean database state
  • Uses transactions internally when possible

Every test starts with:

  • a fresh schema
  • no leftover data
  • consistent environment

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.


1. Slower

Running migrations repeatedly can add overhead.

2. Heavier setup

Especially noticeable in large applications.


The DatabaseTransactions trait takes a different approach.

use Illuminate\Foundation\Testing\DatabaseTransactions;

class UserTest extends TestCase
{
    use DatabaseTransactions;
}
PHP
  • Starts a database transaction before each test
  • Rolls it back after the test finishes

Instead of resetting the database:

  • changes are never committed
  • everything is undone automatically

1. Fast

Transactions are significantly quicker than migrations.

2. Lightweight

No need to rebuild the database schema.


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

Here’s a clear comparison:

FeatureRefreshDatabaseDatabaseTransactions
SpeedSlowerFaster
IsolationHighMedium
Works with queuesYesNo
Works across connectionsYesNo
Setup complexityMediumLow

Use RefreshDatabase when:

1. You are testing queues or jobs

Jobs often run outside the main transaction.

dispatch(new ProcessOrderJob($order));
PHP

If 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.


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.


Mixing both traits

use RefreshDatabase, DatabaseTransactions;
PHP

This creates unpredictable behavior.

Fix: Choose one strategy per test class.


Using transactions with queues

Queue::fake();
PHP

Even 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.


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',
    ]);
}
PHP

With DatabaseTransactions

  • job may not see the order
  • test fails unexpectedly

With RefreshDatabase

  • data is committed
  • job works correctly
  • test passes

For most Laravel applications, the safest default is:

use RefreshDatabase;
PHP

Then optimize later if needed.


You can:

  • use RefreshDatabase for feature tests
  • use DatabaseTransactions for isolated unit-like tests

This gives you:

  • reliability where needed
  • speed where possible

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.


  • 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.

Leave a Reply

Your email address will not be published. Required fields are marked *