Why should a Laravel Dusk test never use RefreshDatabase or an in-memory SQLite database, and which trait do you use instead?
answer
- two processes, two connections
- uncommitted rows are invisible
- :memory: lives inside one process
- DatabaseMigrations: fresh each test
- DatabaseTruncation: migrate once, truncate
basics
~20 sRefreshDatabase keeps each test's writes in an uncommitted transaction, but Dusk's browser hits a separate server process with its own connection, which cannot see them; :memory: SQLite is per-process too. Use DatabaseTruncation or DatabaseMigrations on a shared database.
solid answer
~40 sIn a Dusk test, the test code and the application run in **different processes**: the test creates a user through its own database connection, while Chrome's requests hit the app served at `APP_URL`, which opens another connection. `RefreshDatabase` wraps each test in a transaction and rolls it back, so rows the test inserts are uncommitted and invisible to the app; the sign-up test's pre-existing user simply does not exist for the server. An in-memory SQLite database is worse: it exists only inside one process. Use `DatabaseMigrations`, which runs `migrate:fresh` before each test and `migrate:rollback` after, or `DatabaseTruncation`, which migrates fresh on the first test and afterwards only truncates tables (skipping the migrations table), which is usually faster. Point Dusk at a real database, ideally through `.env.dusk.local`.
go deeper
Remember the rule: no RefreshDatabase in Dusk tests; use DatabaseMigrations or DatabaseTruncation instead.
Explain the two-process model: uncommitted transaction data is invisible to the server, and :memory: SQLite cannot be shared.
Choose DatabaseTruncation for speed, tune its table lists, and isolate Dusk on its own database through .env.dusk files so runs never wipe real data.
Weigh the cost of committed-data isolation in browser suites against coverage, keeping the browser layer thin and the database reset strategy explicit.
## Two processes, not one An HTTP feature test (`$this->post('/register')`) runs the request **inside the test process**: the same PHP process, the same database connection. A Dusk test is different. The test process drives Chrome over WebDriver; Chrome sends real HTTP requests to the application at `APP_URL`; that application is a **separate PHP process** (from `php artisan serve`, Sail, or a local web server) with its **own database connection**. Everything about database isolation follows from that split. ## Why `RefreshDatabase` fails `RefreshDatabase` migrates once and then begins a database transaction at the start of each test, rolling it back at the end. That is fast and clean when everything uses one connection. Under Dusk: 1. The test creates a user, `User::factory()->create(['email' => '[email protected]'])`, inside the open transaction. 2. The browser submits the sign-up form with that email. 3. The server process queries `users` on its own connection. The row is **uncommitted**, so the server cannot see it, and the "email already taken" error never appears. 4. Data the **server** writes, such as the newly registered user, is committed on the server's connection and is **not** rolled back by the test's transaction, so it leaks into the next test. The Dusk docs put it directly: transactions are not applicable across HTTP requests, so Dusk tests should never use `RefreshDatabase`. ## Why `:memory:` SQLite fails The skeleton's `phpunit.xml` sets `DB_CONNECTION=sqlite` and `DB_DATABASE=:memory:` for fast feature tests. An in-memory SQLite database lives **inside one process**. The server process opening `:memory:` gets a brand-new, empty database. Dusk needs a database both processes can open: a SQLite file on disk, or a MySQL/PostgreSQL database. ## The two traits that work | Trait | Before each test | After each test | Speed | |---|---|---|---| | `DatabaseMigrations` | `migrate:fresh` | `migrate:rollback` | slowest: rebuilds the schema every time | | `DatabaseTruncation` | first test: `migrate:fresh`; later tests: truncate tables, then seed if configured | nothing | faster: schema built once | After truncating, `DatabaseTruncation` runs the seeder configured on the test class (a `$seeder` class or `$seed = true`), so reference data such as countries can be restored for each browser test. `DatabaseTruncation` can be tuned with properties on the test class (or on `DuskTestCase` when using Pest): - `$tablesToTruncate`: truncate only these tables. - `$exceptTables`: truncate everything except these; the migrations table is always excluded. - `$connectionsToTruncate`: which connections to clean. - `beforeTruncatingDatabase()` and `afterTruncatingDatabase()` hooks. Both traits work through committed data, so the server sees exactly what the test created. ## Keeping Dusk away from real data Because both traits wipe tables, the database Dusk uses must be disposable. Create a `.env.dusk.local` (or `.env.dusk.{environment}`) with its own `DB_DATABASE`. `php artisan dusk` backs up `.env`, copies the Dusk file into place for the run, and restores the original afterwards. The web server must be using that same database configuration, or the test and the app will talk to different databases. ## Summary - Test process and server process use different connections. - `RefreshDatabase`'s rolled-back transaction hides test data from the server and leaves the server's data behind. - `:memory:` SQLite cannot be shared across processes. - Use `DatabaseTruncation` (usually) or `DatabaseMigrations`, against a disposable database named in `.env.dusk.local`.
- A Dusk suite uses DatabaseTruncation and the sign-up test fails because the email already exists. Where did the row come from?`DatabaseTruncation` cleans tables at the start of each test after the first, so a leftover row usually means the table is listed in `$exceptTables`, not covered by `$tablesToTruncate`, or lives on a connection missing from `$connectionsToTruncate`. It can also mean the server is using a different database than the test because it did not pick up `.env.dusk.local`.
- Why is DatabaseTruncation usually faster than DatabaseMigrations for Dusk?`DatabaseMigrations` runs `migrate:fresh` before every test and `migrate:rollback` after it, rebuilding the whole schema each time. `DatabaseTruncation` runs `migrate:fresh` only on the first test, then just truncates tables, which is far cheaper once the schema has many migrations.
RefreshDatabase in a Dusk test is like drafting a guest list in pencil in your own notebook and then asking the doorman, who has his own list, to let those guests in; he never sees your draft, and whoever he admits stays on his list after you erase yours.
saying these in an interview costs you the question
- RefreshDatabase works in Dusk because the browser shares the test's connection
- An in-memory SQLite database is visible to the web server process
- DatabaseTruncation re-runs every migration before each test
- Rows written by the server are rolled back by the test's transaction
- Dusk can safely run against the development database