WordPress Development

Unit Testing WordPress Plugins with PHPUnit

Unit Testing WordPress Plugins with PHPUnit

For a long time, my testing process for WordPress plugins was manual. Make a change, click around the admin, submit a form, check that nothing looked broken. It worked, mostly, until a small change in one place broke something in another place I didn’t think to check. The bug reached production, and a client found it.

Automated tests solve this. A test suite checks that your code behaves as expected every time you make a change, in seconds. It catches regressions, documents how code is supposed to work and gives you confidence to refactor and update.

WordPress has a mature testing framework built on PHPUnit. Setting it up takes a little effort the first time, but once it’s running, it pays off quickly. Here’s how to get started.

Types of tests

A few terms first:

  • Unit tests test small pieces of code in isolation, like a single function that calculates a price.
  • Integration tests test code working with WordPress, like a function that saves post meta or a hook that modifies content. In WordPress, many “unit tests” are really integration tests, because they run inside a WordPress environment with a database.
  • End-to-end tests test the whole site through a browser, like clicking through checkout. These use different tools, like Playwright.

This guide focuses on PHPUnit tests that run inside WordPress, the most common type for plugins.

Setting up the test environment

Option 1: Scaffolding with WP-CLI

WP-CLI can generate the testing setup for a plugin:

This creates:

  • A phpunit.xml.dist configuration file.
  • A tests folder with a bootstrap file and a sample test.
  • A bin/install-wp-tests.sh script that downloads the WordPress test library and sets up a test database.

Run the install script with database details for a separate test database:

Use a dedicated test database. The test suite resets data during tests, so never point it at a real site’s database.

Option 2: wp-env

wp-env, the WordPress project’s Docker-based environment tool, includes a ready-made test environment. With a .wp-env.json file in your plugin, you can run tests inside the container without setting up a test database manually. It’s often the easiest route for new projects.

Dependencies

Install PHPUnit and the polyfills that help tests run across PHP and PHPUnit versions with Composer:

Check the WordPress test library’s documentation for the supported PHPUnit versions, because they change over time.

Writing your first test

Test classes extend WP_UnitTestCase, which provides WordPress-specific helpers and resets the database between tests:

Run the tests:

Each method starting with test is a test case. Assertions, like assertSame, assertTrue and assertCount, check expected results.

Using factories to create test data

WP_UnitTestCase provides factories to create posts, users, terms and comments quickly:

Factories make tests short and readable. Data created during a test is rolled back afterwards, so tests don’t affect each other.

Testing hooks

Many plugins work by hooking into WordPress. Test that hooks are registered and behave correctly:

You can also check that a hook is registered with has_action or has_filter.

Testing user capabilities

Security checks deserve tests. Create users with different roles and confirm permissions:

Tests like these catch missing capability checks, one of the most common sources of plugin vulnerabilities.

Testing REST API endpoints

WordPress lets you dispatch REST requests in tests without HTTP:

Testing permissions on every endpoint is one of the most valuable things a plugin test suite can do.

Mocking external requests

Tests shouldn’t call real external APIs. They’re slow, unreliable and may cost money. Intercept HTTP requests with the pre_http_request filter:

Now your code receives a predictable response, and you can test how it handles success, errors and unexpected data.

What to test first

You don’t need complete coverage to benefit. Start with:

  • Business logic: calculations, pricing, eligibility rules.
  • Security checks: capabilities, nonces and REST permissions.
  • Bugs you’ve fixed: write a test that reproduces the bug before fixing it, so it never returns.
  • Code you’re about to refactor: tests let you change structure safely.
  • Data handling: saving and sanitising user input.

Structure your code for testability

Code is easier to test when:

  • Functions do one thing and return values rather than echoing output.
  • Business logic is separate from WordPress hooks and output.
  • Dependencies, like API clients, can be replaced in tests.

You don’t need a complex architecture. Small, focused functions go a long way.

Running tests automatically

Tests are most valuable when they run on every change. Continuous integration services, like GitHub Actions, can run your test suite on every push and pull request, across multiple PHP and WordPress versions.

A typical CI workflow:

  1. Check out the code.
  2. Set up PHP and a database service.
  3. Install Composer dependencies.
  4. Install the WordPress test library.
  5. Run PHPUnit.

If tests fail, the change doesn’t get merged. That simple rule prevents many bugs from reaching users.

Run tests against the minimum and latest PHP and WordPress versions you support, to catch compatibility issues early.

Beyond PHPUnit

  • Coding standards checks with PHP_CodeSniffer catch security and style issues.
  • Static analysis with tools like PHPStan finds type errors and bugs without running code.
  • End-to-end tests with Playwright test real browser flows, like checkout or block editor behaviour.
  • JavaScript tests for block editor code.

Together, these form a safety net that lets you ship updates confidently.

A realistic way to start

If your plugin has no tests at all, don’t try to cover everything at once. Start with one test file for the most important function, perhaps the one that calculates prices or checks permissions. Get it running locally and in CI. Then add a test every time you fix a bug or add a feature. Within a few months, you’ll have meaningful coverage of the code that matters most, built naturally as part of normal work.

I’ve found that the first test is the hardest, mainly because of setup. Once the infrastructure works, adding tests becomes routine, and the confidence they give when updating or refactoring code is hard to give up.

Keep tests fast and independent

Slow test suites don’t get run. Keep tests focused, avoid real network calls and create only the data each test needs. Each test should work on its own, in any order, without depending on data left behind by other tests. WP_UnitTestCase’s automatic cleanup helps, but be careful with options and global state your code changes.

Test the edge cases

Bugs often hide at boundaries: empty inputs, zero values, missing data, very long strings, unexpected types and users without the right permissions. When writing tests, include at least one edge case for every normal case. These are exactly the situations that manual testing tends to skip and real users tend to find.

Read failing tests carefully

When a test fails after a change, resist the urge to update the test to make it pass. First ask whether the test caught a real problem. Often it did. Tests are most valuable precisely at the moment they disagree with you.

Tests as documentation

Well-named tests describe how your code is supposed to behave. A test called test_subscriber_cannot_export_orders tells future developers exactly what the rule is. When someone wonders whether a behaviour is intentional, the test suite answers the question, and it keeps answering it accurately as the code changes.

That’s one of the quieter benefits of testing: it preserves knowledge that would otherwise live only in someone’s memory or in an old ticket nobody can find.

Coverage isn’t the goal

Code coverage numbers are useful indicators but poor targets. A suite that covers every line with weak assertions catches fewer bugs than a smaller suite that tests important behaviour thoroughly. Focus on testing what matters most to users and to security, and let coverage grow naturally from there.

Start small, and keep going.

Frequently asked questions

Do small plugins need tests?

Even small plugins benefit from a few tests around their core logic and security checks. Tests prevent regressions as the plugin evolves.

Will tests affect my live site?

No. Tests run against a separate test database and environment. Never point the test suite at a production database.

How long does setting up testing take?

With WP-CLI scaffolding or wp-env, you can have a basic test running within an hour. Writing a useful suite grows over time.

What should I test if I can only write a few tests?

Security checks, core business logic and any bug you’ve previously fixed.

Final thoughts

Automated tests turn “I hope this still works” into “I know this still works”. Scaffold the test setup with WP-CLI or use wp-env, write tests with WP_UnitTestCase and factories, focus first on business logic and security, mock external requests and run tests automatically on every change. Start small, add tests as you fix bugs and your plugins will become more reliable with every release.