Development

How to Test a WordPress Plugin Across PHP Versions With Lando

Testing a WordPress plugin across PHP versions with Lando is useful when the local matrix is repeatable and disposable. The goal is not to click through the site once. It is to prove installation, activation, critical plugin behavior, upgrades, and cleanup against every runtime you claim to support.

Lando can rebuild the application service with a different PHP version while preserving the database by default. That makes runtime switching convenient, but it also means one test run can affect the next unless you control fixtures and reset state deliberately.

The examples assume the repository already contains an installed WordPress tree under wordpress/, the plugin under test, a locked Composer test toolchain, a WordPress test bootstrap, and sanitized fixtures. Lando provides the services and command routing; the Landofile alone does not download WordPress, map the plugin, or create a PHPUnit bootstrap.

Choose a supported matrix

Start from three sources:

  • The plugin's documented minimum and maximum PHP versions
  • The PHP project's currently supported releases
  • The WordPress project's current server recommendations and compatibility

Do not test a version merely because a container image exists. Decide which versions are supported for production, which are compatibility-only, and which upcoming version is an early-warning lane.

At minimum, include the plugin's lowest supported PHP version and the newest version you intend to support. Intermediate versions are valuable when language behavior, dependencies, or extensions differ.

Keep one canonical Lando recipe

A basic .lando.yml can make the runtime explicit:

name: plugin-compatibility
recipe: wordpress
config:
  webroot: wordpress
  php: '8.3'
  database: mysql:8.0
services:
  appserver:
    overrides:
      environment:
        WP_ENVIRONMENT_TYPE: local
tooling:
  wp:
    service: appserver
    dir: /app/wordpress
  plugin-test:
    service: appserver
    dir: /app
    cmd: /app/vendor/bin/phpunit

Verify the currently supported recipe values in the Lando WordPress configuration reference before selecting versions. Keep the database version fixed while testing PHP unless database compatibility is also the subject. Changing both axes at once makes failures harder to attribute.

For a manual matrix, change only the config.php value between runs and rebuild. For a team workflow, keep small version-specific override files or generate the final config through a reviewed script. Avoid several copied recipes that drift in unrelated settings.

Back up before the first destructive reset

lando rebuild rebuilds containers and normally preserves application data. lando destroy removes the app and its database volume.

Before experimenting with install or upgrade paths, export a known baseline:

lando start
lando wp core version
lando wp db export /app/.lando/fixtures/plugin-baseline.sql

Keep sanitized fixtures outside the public webroot and out of Git if they contain private data. A database export is not a substitute for a tested production backup.

The Lando rebuild documentation explains the data-preserving behavior. Still inspect the plan before confirming any destructive command.

Rebuild one PHP version at a time

For each matrix entry:

  1. Update the PHP version in the Lando configuration.
  2. Rebuild the services.
  3. Record the effective runtime and extensions.
  4. Install locked development dependencies.
  5. Reset WordPress to the known fixture state.
  6. Activate the plugin and run the suite.
lando rebuild -y
lando php -v
lando php -m
lando composer install --no-interaction
lando wp core version

Do not infer the container's runtime from .lando.yml. Capture lando php -v in the test output so a failed or cached rebuild cannot create a false result.

Test installation and activation explicitly

A test matrix should include a clean activation path, not only an already-active database carried across rebuilds.

lando wp plugin deactivate sample-plugin

if lando wp cron event list --field=hook | grep -Fxq 'sample_plugin_sync'; then
  echo 'The plugin left its scheduled event behind.' >&2
  exit 1
fi

lando wp plugin activate sample-plugin
lando wp plugin is-active sample-plugin
lando wp plugin status sample-plugin
lando wp eval '
if (!shortcode_exists("sample_download")) {
    WP_CLI::error("Expected shortcode was not registered.");
}
'

Replace sample_plugin_sync and sample_download with a scheduled hook and public surface the plugin actually promises. wp eval is a real WP-CLI command, and shortcode_exists() makes this example an executable registration assertion rather than an invented plugin command. Add a project integration test for the shortcode's rendered behavior too.

Do not deactivate the target plugin with --skip-plugins. That flag prevents normal plugin loading, so the target's bootstrap and registered deactivation callback may be absent from the lifecycle being tested. Reserve skip flags for recovery from a fatal plugin or for deliberately isolating unrelated plugins, then repeat the representative pass with the target and required companions loaded normally.

Capture PHP warnings and deprecations. A command exiting zero while filling the error log is not a clean compatibility result.

Exercise critical WordPress surfaces

The exact suite depends on the plugin, but a useful smoke layer covers:

  • Clean activation and deactivation
  • Database schema creation and upgrade routines
  • Front-end rendering or shortcodes
  • Administrative screens and form submissions
  • REST API routes
  • WP-Cron callbacks and queued jobs
  • AJAX handlers
  • Capability and nonce enforcement
  • Uninstall or data-retention behavior
  • Integration hooks promised to other plugins

Use WP-CLI to make repeatable setup and assertions. For example, a content plugin might create a fixture post, store the state expected by its shortcode, and verify that the plugin registered that public surface:

POST_ID=$(lando wp post create \
  --post_type=post \
  --post_status=publish \
  --post_title='Compatibility Fixture' \
  --porcelain)

lando wp post meta update "$POST_ID" sample_access protected
lando wp eval '
if (!shortcode_exists("sample_download")) {
    WP_CLI::error("Expected shortcode was not registered.");
}
'

The project integration suite should then render the shortcode for the fixture and assert its authorized and unauthorized outcomes. If this becomes a checked-in shell script, enable strict error handling and clean up created records. Never run the script against an environment whose target is ambiguous.

Preserve data for rebuilds, reset it for isolation

Rebuilding for another PHP version should not destroy the database unintentionally. Test data isolation is a separate decision.

Three useful reset strategies are:

  • Restore a sanitized SQL fixture before every matrix entry.
  • Use test-suite transactions when application and test connections share them safely.
  • Create a fresh disposable Lando app for each entry.

To restore a Lando database from a fixture, use the documented lando db-import workflow. The import command wipes the target database by default, so confirm that the current app is disposable and the dump is inside the application directory before running it.

For upgrade testing, do not reset between the “old plugin” and “new plugin” steps. That flow needs authentic pre-upgrade data:

  1. Restore the old-version fixture.
  2. Activate the old plugin version.
  3. Create representative records.
  4. Replace the code with the candidate version.
  5. Run its upgrade path once.
  6. Assert schema, data, permissions, and idempotency.
  7. Run the upgrade entry point again and confirm it does no damage.

Add a failure-focused test pass

Compatibility failures often appear only outside the happy path. Include malformed REST input, missing optional extensions, unauthorized requests, empty datasets, large values, time-zone boundaries, and failed external calls.

If the plugin integrates with payments or email, use sandbox endpoints or fakes. A local compatibility matrix must not charge customers or send production messages.

Mirror the matrix in CI

Lando is a convenient developer interface, while CI may use native service containers. That is acceptable if both describe the same PHP extensions, database version, WordPress version, dependency lockfile, configuration, and test commands.

Make the runtime visible in every job:

php -v
composer validate --strict
composer install --no-interaction --prefer-dist
vendor/bin/phpunit

Add WordPress installation and WP-CLI smoke steps for behavior PHPUnit does not boot realistically. Test the lowest and newest supported PHP versions on every pull request. A wider scheduled matrix can cover additional WordPress and database versions.

Record results as support evidence

For each matrix combination, retain:

  • Effective PHP, WordPress, and database versions
  • Composer lockfile identity
  • Test and smoke commands
  • Exit status and deprecation output
  • Known exclusions
  • Date and commit tested

Passing a matrix once does not make it permanently true. Re-run it when PHP, WordPress, dependencies, build images, or plugin behavior changes.

A good Lando matrix gives developers a fast local reproduction path, protects data during ordinary rebuilds, and makes destructive resets intentional. Pair it with CI so compatibility is a maintained claim rather than a release-day guess.