In Laravel, what does php artisan test add over vendor/bin/phpunit, and how does make:test decide which file it writes?
answer
- same tests, friendlier runner
- runner options passed straight through
- --parallel and --profile
- Tests\Feature by default, --unit for Unit
- tests/Pest.php switches to Pest stubs
basics
~20 sphp artisan test runs the same Pest or PHPUnit suite with readable output, forwards runner options and adds --parallel and --profile. make:test writes a tests/Feature test by default, a tests/Unit one with --unit, and Pest files when Pest is detected.
solid answer
~30 s`php artisan test` does not replace the runner: it runs your Pest or PHPUnit suite with verbose, readable output and passes through any option the runner accepts, such as `--testsuite=Feature --stop-on-failure`. It adds Laravel-specific flags: `--parallel` (with `brianium/paratest` installed) and `--profile`, which lists the ten slowest tests. `make:test RenewalTest` writes `tests/Feature/RenewalTest.php` in the `Tests\Feature` namespace extending `Tests\TestCase`; `--unit` writes to `tests/Unit` extending `PHPUnit\Framework\TestCase`. It writes a Pest file instead when you pass `--pest`, or automatically when Pest is installed and `tests/Pest.php` exists; `--phpunit` forces a class. A path such as `Billing/RenewalTest` creates the subfolder.
code
bash · 11 linesphp artisan make:test Billing/RenewalTest
# -> tests/Feature/Billing/RenewalTest.php, extends Tests\TestCase
php artisan make:test BoxPriceCalculatorTest --unit
# -> tests/Unit/BoxPriceCalculatorTest.php, extends PHPUnit\Framework\TestCase
php artisan make:test RenewalReminderTest --pest
# -> a Pest file, whatever the project detects
php artisan test --testsuite=Unit --stop-on-failure
php artisan test --profilego deeper
Know the three ways to run tests, that make:test defaults to tests/Feature, and that --unit writes a plain PHPUnit class in tests/Unit.
Explain how make:test chooses Pest or PHPUnit (flags first, then Pest installed plus tests/Pest.php) and that php artisan test forwards runner options.
Use the wrapper's extras deliberately: --profile to find the slow tests before tuning, --parallel with paratest, and composer test to rule out a stale config cache.
Decide the team's runner conventions: one documented command for local and CI runs, Pest or PHPUnit as the house style, and customized stubs so generated tests start from the team's patterns.
## Three ways to run the same suite A Laravel application can run its tests with any of three commands, and all three execute the same test files: | Command | What it is | Typical use | |---|---|---| | `./vendor/bin/phpunit` | PHPUnit's own binary | a PHPUnit-only suite, IDE integrations | | `./vendor/bin/pest` | Pest's binary | a Pest suite | | `php artisan test` | Laravel's Artisan wrapper around whichever runner the app uses | day-to-day runs, CI, parallel runs | The skeleton's `composer.json` also defines a `composer test` script that clears the configuration cache and then runs `php artisan test`, which is the safest one-word way to run a suite locally. ## What the Artisan wrapper adds `php artisan test` is a convenience layer, not a different engine: - **Readable output.** It prints a per-test report with clear failure blocks, which the docs describe as easing development and debugging. - **Option pass-through.** Any argument the underlying runner accepts can be passed, for example `php artisan test --testsuite=Feature --stop-on-failure`. You do not lose runner features by using it. - **`--parallel`.** Runs the suite across several processes. It needs the `brianium/paratest` dev dependency; the framework's `composer.json` lists it as the package "required to run tests in parallel". - **`--profile`.** Lists the **ten slowest tests** after the run, which is the first thing to look at when a suite gets slow. Because the wrapper is just a front end, a failing test fails the same way under all three commands. ## How make:test picks the file `php artisan make:test` is the generator for test files. Its decisions, read from `TestMakeCommand`: 1. **Folder and namespace.** Without options the class goes to `tests/Feature` in the `Tests\Feature` namespace. With `--unit` (or `-u`) it goes to `tests/Unit` in `Tests\Unit`. A name with a path, such as `Billing/RenewalTest`, creates `tests/Feature/Billing/RenewalTest.php`. 2. **PHPUnit or Pest.** `--phpunit` forces a PHPUnit class. `--pest` forces a Pest file. With neither, the command uses Pest only when Pest's `version()` function exists **and** a `tests/Pest.php` file is present; otherwise it writes a PHPUnit class. 3. **Stub.** The four built-in stubs are `test.stub` (feature, extends `Tests\TestCase`, imports `RefreshDatabase` and `WithFaker` without using them, and asserts a `GET /` returns 200), `test.unit.stub` (extends `PHPUnit\Framework\TestCase`), and `pest.stub` / `pest.unit.stub`. If your application has a `stubs/test.stub` file at its root, that customized copy wins. 4. **Interactive prompt.** If you run the command without a name, it prompts for one and then asks whether you want a Feature or Unit test. ## Which default a new project gets The laravel/laravel skeleton itself ships PHPUnit (`phpunit/phpunit ^12.5.12`) and no Pest. The `laravel new` installer, however, **defaults to Pest** unless you pass `--phpunit`, and converts the example tests. So on a project created with the installer, a bare `make:test` usually writes Pest files, while a project cloned from the skeleton writes PHPUnit classes. Both are first-class; the docs show every example in both styles. ## A subscription-box example Imagine a subscription-box app with a renewal endpoint and a price calculator: - `php artisan make:test Billing/RenewalTest` gives a feature test for `POST /subscriptions/{id}/renew`, able to use the database and HTTP helpers. - `php artisan make:test BoxPriceCalculatorTest --unit` gives a plain PHPUnit class for the pure pricing rules. - `php artisan test --testsuite=Unit` runs just the fast ones while iterating; `php artisan test --profile` shows which renewal tests dominate the run time. ## Habits that keep the loop short A few everyday habits come straight from these tools: - Generate tests with `make:test` rather than copying files, so the namespace, folder and base class always agree and a unit test never accidentally extends `Tests\TestCase`. - Keep one documented command for the whole team, usually `composer test` or `php artisan test`, so local and CI runs behave the same. - Pass runner options through the wrapper while iterating, for example `--stop-on-failure`, instead of switching to a different binary. - Run `--profile` now and then, not only when someone complains, because slow tests creep in one at a time. ## What interviewers listen for - That `php artisan test` and `vendor/bin/phpunit` run the **same** tests, and the wrapper forwards options rather than replacing them. - That `--parallel` needs paratest and that `--profile` is the measuring tool. - That `make:test` defaults to a **feature** test, and the Pest-versus-PHPUnit choice follows the project's setup rather than a global default. A candidate who says the Artisan command is "required" to run Laravel tests, or that it uses its own assertion engine, has misread what it is.
- Why might php artisan make:test write Pest files on one project and PHPUnit classes on another?Without `--pest` or `--phpunit`, the command checks whether Pest is installed and whether `tests/Pest.php` exists. Projects created with `laravel new` default to Pest and have that file; a project cloned from the laravel/laravel skeleton ships PHPUnit only, so it gets PHPUnit classes.
- Why is composer test a safer habit than typing php artisan test directly?The skeleton's `composer test` script clears the configuration cache before running `php artisan test`. If someone cached config locally, tests would otherwise boot with that cached config and ignore the testing environment values in `phpunit.xml`, which can point them at the wrong database.
saying these in an interview costs you the question
- php artisan test uses its own assertion engine instead of PHPUnit or Pest.
- Options like --stop-on-failure cannot be passed to php artisan test.
- make:test puts new tests in tests/Unit unless you ask for a feature test.
- --parallel works out of the box without installing any package.
- make:test always writes PHPUnit classes; Pest files must be written by hand.