I am spoiled by the many test watchers from the javascript world that do all the file change watching and polling on your behalf, to rerun the tests the moment you save a particular file. This feature usually comes out of the box, especially with the more complex tools like Jest or Cypress.

Trying to do the same thing with PHPUnit, the standard PHP testing framework used by Laravel too, I found automatic test running on file change not included as a first-class citizen. I have found multiple packages that could be installed by composer, buy all of them did not appeal to me.

I am a script kiddie

Yes, StackOverflow copy-paste to the rescue. Again. The dead-simple solution written in Bash could be found in this answer and looks like this:

#!/bin/bash
while true; do
  FILE=$(inotifywait --exclude=".*.*sw*" --exclude="4913" ./ --format "%w%f" -e close_write) &&
  clear &&
  phpunit --color $FILE
done

Kudos for the user Tango Bravo for providing it. The author also claims the script is vim compatible, due to the magic number 4913 which I do not understand unfortunately. As a proper script-kiddie, not understanding every part of the script did not prevent me putting the script into the Laravel project directory, made it executable and run it.

Not part of the playground: inotifywait

Yeah, expecting something to run on the first time might be foolish:

./watch.sh: line 3: inotifywait: command not found

If you follow my posts for long enough, where long enough means something around three months worth of my blogging career, which is hilariously short time, you probably know how to determine which package provides the file. I did exactly that:

$ pkgfile inotifywait
extra/bash-completion
community/inotify-tools

A tough choice for a zsh user

sudo pacman -S inotify-tools

The script now runs without command not found complains.

Sail to the distant shores

The first optimization that I did with the running watch script was to change the test command. With the Laravel 8, the Laravel Sail is available. I had it running already, so why not use it?

#!/bin/bash
while true; do
  FILE=$(inotifywait --exclude=".*.*sw*" --exclude="4913" ./ --format "%w%f" -e close_write) &&
  clear &&
-  phpunit --color $FILE
+  ./vendor/bin/sail artisan test
done

Still, editing any project file deeper in the directory structure did not trigger the test run. We need to go recursive for that:

#!/bin/bash
while true; do
  FILE=$(inotifywait --recursive --exclude=".*.*sw*" --exclude="4913" ./ --format "%w%f" -e close_write) &&
  clear &&
  ./vendor/bin/sail artisan test
done

This works reasonably well, but it can get a little bit better still.

Focus please

One problem with the above is that a mere git status triggers the test run as the command probably writes some file into the .git/ directory (I did not test, but it is the most likely reason). Luckily, we just need a monitor a few key directories, namely app/, tests/, routes/ and possibly resources/. They can be specified easily:

#!/bin/bash
while true; do
  FILE=$(inotifywait --recursive --exclude=".*.*sw*" --exclude="4913" ./app ./tests  ./routes ./resources/ --format "%w%f" -e close_write) &&
  clear &&
  ./vendor/bin/sail artisan test
done

I keep this script running on the side monitor. Currently it works well enough. Happy testing!