vectorface/dunit
Vectorface's DUnit is a PHPUnit extension for database unit testing. It simplifies loading fixtures, isolating tests with transactions, and asserting on database state, helping PHP developers verify SQL-backed code against real databases in a familiar, lightweight PHPUnit workflow.
First, confirm which dunit you have. The v2.1.0 release notes describe a Docker-based PHPUnit runner (a CLI with dunit --pull, bundled Docker images including php-nightly, signal handling, $COLUMNS-aware progress output). That is a different project from the PHPUnit base-class/PDO table-assertion library (Vectorface\DUnit\TestCase) covered in earlier revisions of this assessment. Before you install anything:
composer show -a <vendor>/dunit # read the repository URL and description
bin/dunit executable and a Docker image matrix, use this assessment.src/Vectorface/DUnit/TestCase.php and assertTableEquals-style helpers, that is the 2015-era assertion library — treat it as legacy and do not adopt it for new work.Minimal steps for the runner (v2.1.0):
composer require --dev <vendor>/dunit
dunit invocation shells out to docker run.master.vendor/bin/dunit --help — confirm --pull is present, which tells you you are on ≥ 2.1.0.Dockerfile/image list in the repo to see which PHP versions are offered and what each image contains.php-nightly to preview breakage on the next PHP.dunit against pinned tags for reproducibility. Put dunit --pull in a scheduled nightly/weekly job that only refreshes the local image cache. Pulling on every CI run adds latency, and a floating tag can turn a green build red without a code change.# nightly: refresh the local image cache only
- run: vendor/bin/dunit --pull
# pull-request: run tests against whatever is cached/pinned
- run: vendor/bin/dunit
Use php-nightly as an advisory job. The notes describe it as a build of PHP 7 beta from source. Mark that job continue-on-error / allow-failure so an upstream PHP change never blocks a merge, but you still get early warning.
Leverage the smaller squashed images, but cache them. The 2.1.0 images are "much smaller downloads" and "fairly minimal Debian-based" — cold pulls got cheaper, but in CI you should still persist the Docker layer cache between runs (registry cache or docker save/docker load) so your matrix does not re-download on every build.
Set COLUMNS explicitly in CI. The $COLUMNS fix means PHPUnit progress output now line-breaks correctly — but that only happens when the value is known. CI runners frequently have no TTY and no COLUMNS, so export it:
COLUMNS=120 vendor/bin/dunit
timeout --signal=INT 900 vendor/bin/dunit
Build your own images from the base ones. The runner gives you the PHP runtime, not your application's extensions. Extend the minimal Debian base image in your repo, install the extensions your app needs, and pin the PHPUnit version inside the image to what your composer.json actually resolves to.
For Laravel apps, wire services through the config, not 127.0.0.1. The runner only executes PHPUnit inside a container. Anything your test suite talks to (MySQL, Redis, mail) must be declared as a service in the runner config and addressed by service hostname; DB_HOST=127.0.0.1 inside the container refers to the container itself. Keep .env.testing values overridable by environment variables.
Keep a composer install step in the image or an entry step. The container has PHP, not your vendor/ directory; make sure the cached dependency install is part of the image build or the documented bootstrap, not something you do by hand.
Name collision is the top risk. Two unrelated packages are called dunit. The Docker runner has no Laravel integration, no RefreshDatabase interop, and no table-snapshot assertions. The PDO assertion library has no CLI and no images. If you expected assertTableEquals, you have the wrong package installed — check composer.lock and the repo URL before debugging anything else.
--pull is a mutating, network-dependent operation. It pulls $image for each configured image. Floating tags (latest, php-nightly) will silently move. Never put --pull in a job you expect to be deterministic; pin tags or digests for the test job and use --pull only to refresh.
The 2.1.0 image rework can break custom Dockerfiles. Squashed/minimal images may have dropped packages, build tools, or preinstalled extensions. If you build FROM these images, run one docker build --no-cache after upgrading and diff the installed-module list (php -m) before assuming your Dockerfile still works.
Old layer caches are stale, not wrong. After upgrading, a cached image ID can keep your build on the pre-squash base. Prune or re-pull once when you bump the runner version.
php-nightly is a source build of a beta PHP. Expect it to fail for reasons unrelated to your code, and expect it to be rebuilt/renamed without notice. Never gate releases on it.
Signal handling only helps if the signal is delivered. docker kill (SIGKILL) still leaves no chance to clean up. Use timeout --signal=INT (or your CI's graceful-cancel setting) and add a belt-and-braces cleanup step:
docker ps -a --filter "label=dunit" # then remove leftovers
Progress output is TTY-sensitive. A missing COLUMNS in CI can still render one long, unreadable line even on 2.1.0; export it or run with --no-progress/verbose output if the version supports it.
Debugging a failed run. Reproduce outside the runner: docker run --rm -it <image> bash, install deps, run vendor/bin/phpunit with the same args. Also check docker ps -a for containers left behind by a failed/killed run — they can hold ports and confuse the next invocation.
Extension points are configuration and images only. You cannot subclass or monkey-patch the runner the way you would a PHPUnit base class. If you need extra behaviour (coverage merging, parallel sharding, custom assertions), implement it in your own images, scripts, or CI steps that call dunit — not by forking.
If you actually need database table assertions in Laravel, this package is not the answer. Use RefreshDatabase/DatabaseTransactions with assertDatabaseHas, assertDatabaseCount, and assertDatabaseMissing, or write a small custom assertion around the query builder.
How can I help you explore Laravel packages today?