If you just want a tl:dr, here’s the script you can plop in whatever .bashrc file you’re using—explanation follows.

The pw function runs your tests once by calling phpunit tests , watches every php file in src and tests , and runs the tests again when a watched file changes.

You can specify any PHPUnit arguments after pw , e.g. pw ./tests/Unit/FooTest.php or pw --filter test_true_is_true .

The script uses Facebook’s Watchman library, which—on OSX—can be installed installed via Homebrew.

brew install watchman

Watchman watches files and triggers actions when they change. The reasoning behing choosing Watchman: it’s easy to install, simple to configure, and reliable.

The watchman-make command—which ships with Watchman—is a specialised interface for Watchman to invoke build tools in response to file changes—exactly what we need!

Let’s do a line-by-line review of our watch function.

function pw { }

The function name determines the command name. I like short commands—PHPUnit is aliased to p on my machine—so an abbreviated version of phpunit-watch seems like a good fit.

run="clear && printf '\e[3J' && vendor/bin/phpunit"

Since we’re going to need the actual “run” command twice, let’s store it in a variable. To break it down further, clear && printf ‘\e[3J’ clears the terminal (to keep previous test runs from cluttering it) and vendor/bin/phpunit runs the tests.

[[ -n $@ ]] && args=$@ || args="tests"

watchman-make needs arguments to work. (I’d love to be proven wrong here so I can clean this part up!) We’ll default the arguments to tests, which means the actual command that we’ll run is vendor/bin/phpunit tests . If we provide any arguments to pw , they’ll replace test , for example pw —-stop-on-failure would run vendor/bin/phpunit —-stop-on-failure .

The next two commands bring everything together.

eval "$run $args"

Manually triggers the command once before watching. This way we see immediately see test results without having to change a file first.

watchman-make \

-p 'src/**/*.php' 'tests/**/*.php' \

--make=$run \

-t "$args"

Finally, time for the Watchman part! The -p parameter specifies which folders we want to watch. I personally set up way more globs like app/**/*.php and database/**/*.php since I’m mostly working with Laravel. --make specifies which command we’re going to run on change, and -t will pass extra arguments to the --make command (remember, that variable we defaulted to tests ).

I’m still dreaming of a Jest-like CLI tool for PHPUnit, which also allows you to filter and rerun specific tests without breaking out of the watch function, but being able to run tests on change is already a vast workflow boost.