In Laravel tests, how do #[Seed], #[Seeder], the $seed property and $this->seed() seed data, and when does each one run?
answer
- attributes on the class or a parent
- Seed runs DatabaseSeeder
- Seeder names one class
- seeding rides on migrate:fresh
- DatabaseTransactions ignores both
basics
~20 s#[Seed] or $seed = true makes the reset trait's migrate:fresh run DatabaseSeeder; #[Seeder(X::class)] or $seeder runs one class instead. With RefreshDatabase that happens once per process and every test rolls back to it. $this->seed() runs seeders inside one test.
solid answer
~40 sThe class-level options feed the reset trait's `migrate:fresh` call: `#[Seed]` (or `protected $seed = true`) adds `--seed`, which runs `Database\Seeders\DatabaseSeeder`, and `#[Seeder(WarehouseSeeder::class)]` (or `$seeder`) adds `--seeder` with that class. The attributes are found on the test class or any parent, so `#[Seed]` on `Tests\TestCase` covers the suite, and they win over the properties. When it runs depends on the trait: with `RefreshDatabase` seeding happens with the one migration per process, and each test starts from the seeded state because its changes roll back; `DatabaseMigrations` seeds before every test; `DatabaseTruncation` seeds with its first migration and reseeds after each truncation. `DatabaseTransactions` never migrates, so it ignores all of them. `$this->seed(X::class)` runs `db:seed` on demand inside a single test.
code
php · 12 lines<?php
namespace Tests;
use Illuminate\Foundation\Testing\Attributes\Seed;
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
#[Seed] // DatabaseSeeder runs with the reset trait's migrate:fresh
abstract class TestCase extends BaseTestCase
{
//
}go deeper
Know that #[Seed] runs DatabaseSeeder for tests and that $this->seed() runs a seeder inside one test.
Explain that class-level seeding rides on the trait's migrate:fresh, how #[Seeder] names one class, and that attributes on parents count.
Predict seeding cost per trait, catch #[Seed] silently ignored under DatabaseTransactions, and keep heavy data in the tests that need it.
Decide what reference data the whole suite may assume, and keep it small enough that switching reset traits does not multiply CI time.
## Why tests need seeders at all A warehouse app has reference data that almost every test assumes: stock movement types (`receipt`, `pick`, `adjustment`), warehouse zones, units of measure. Creating those rows with factories in every test is noisy. Seeders already exist to load them, and Laravel's reset traits can run them for you. Writing the seeders and factories themselves is a separate topic; here the question is how tests trigger them and when. ## The four ways to ask for seeding | Option | Where | What runs | |---|---|---| | `#[Seed]` | on the test class or any parent | `Database\Seeders\DatabaseSeeder` | | `protected $seed = true;` | property on the test class | `Database\Seeders\DatabaseSeeder` | | `#[Seeder(WarehouseSeeder::class)]` | on the test class or any parent | that one seeder class | | `protected $seeder = WarehouseSeeder::class;` | property on the test class | that one seeder class | | `$this->seed(...)` | inside a test method | `db:seed` with the given class or classes | The first four are read by a shared helper that builds the options for `migrate:fresh`. It walks the class and its parents looking for the attributes first, then falls back to the properties. A specific seeder wins over the plain seed flag. The current docs show the attributes; the properties still work for older suites. ## When class-level seeding runs, per trait Class-level seeding is not a separate step: it rides on the trait's own migration. 1. **`RefreshDatabase`** runs `migrate:fresh --seed` (or `--seeder=...`) once per PHP process, then wraps each test in a transaction. Every test sees the seeded data, and anything it changes rolls back, so the next test sees the seeded state again. The docs describe this as seeding before each test that uses the trait; mechanically it is seeded once and restored by rollback. 2. **`LazilyRefreshDatabase`** behaves the same, but only when a test first touches the database. 3. **`DatabaseMigrations`** runs `migrate:fresh` with the seed options before every test, so seeding cost is paid per test. 4. **`DatabaseTruncation`** seeds with its `migrate:fresh` on the first test; on later tests it truncates the tables and then runs `db:seed` again with the same class. 5. **`DatabaseTransactions`** does not migrate and does not read these options, so `#[Seed]` on a class using only this trait silently does nothing. ## Seeding inside a test `$this->seed()` with no argument runs `DatabaseSeeder`; `$this->seed(WarehouseZoneSeeder::class)` runs one class; an array runs several in order. Each call runs `db:seed --class=...` inside the test, and therefore inside the test's transaction when a transaction trait is active, so the rows vanish with the rollback. Use it when only a few tests need a heavy data set: seeding it for the whole suite would slow every test. ## Two related knobs The same helper that reads the seed options also reads `$dropViews` and `$dropTypes`. When true, `migrate:fresh` is called with `--drop-views` or `--drop-types`, which matters for databases whose migrations create views or custom types that a plain fresh migration would leave behind. ## Choosing between them - Put `#[Seed]` on `Tests\TestCase` only for small reference data that nearly every test assumes. - Use `#[Seeder(...)]` on the few classes that need a specific data set, such as zone layouts for pick-list tests. - Use `$this->seed(...)` inside the individual tests that need heavy data, so the rest of the suite does not pay for it. - Prefer the attributes in new code; they are what the current docs show, and a reader sees them at the top of the class. ## Pitfalls - **Heavy seeders on `DatabaseMigrations` or `DatabaseTruncation`.** Every test pays for them. Keep suite-wide seeders small and push bulky data into the few tests that need it. - **Seeders that call external services or queue jobs.** They run inside tests too; keep them to plain inserts. - **Tests that depend on seeded IDs.** Reference rows seeded once keep their IDs, but rows created by tests do not; look up seeded rows by a natural key such as `code = 'receipt'`. - **Assuming `#[Seed]` works with every trait.** It does nothing with `DatabaseTransactions`. Interviewers use this question to see whether a candidate understands that seeding is tied to the reset trait's migration, not an independent per-test step.
- A test class uses DatabaseTransactions and #[Seed], but the seeded rows never appear; why?Class-level seeding is passed to the reset trait's `migrate:fresh` call. `DatabaseTransactions` never migrates and never reads the seed options, so nothing runs. Switch to `RefreshDatabase`, or call `$this->seed()` inside the tests, which runs the seeder within the test's transaction.
- Why can #[Seed] make a suite using DatabaseTruncation much slower than one using RefreshDatabase?With `RefreshDatabase` the seeder runs once per process, and each test rolls back to the seeded state. `DatabaseTruncation` empties the tables before every later test and then runs `db:seed` again, so the full seeder runs once per test. Keep suite-wide seeders small or seed per test where needed.
saying these in an interview costs you the question
- #[Seed] runs the seeder in a separate step before every test, whatever trait is used.
- #[Seed] works the same with DatabaseTransactions as with RefreshDatabase.
- $this->seed() inserts rows that survive the test's rollback.
- The #[Seed] attribute must be repeated on every test class; parents are ignored.
- The $seed property overrides a #[Seeder] attribute on the same class.