Master unit testing with PHPUnit in WordPress. Learn to isolate your business logic, mock external dependencies, and build a robust testing suite.
Previously in this course, we explored linting and code quality to enforce structural consistency across our codebase. Now that our code adheres to strict standards, we need to ensure it actually works as expected. This lesson introduces Unit Testing with PHPUnit to verify our business logic in isolation.
Unit testing is the practice of testing the smallest testable parts of your application—usually individual methods within a class—in total isolation. In the context of a WordPress plugin, this means your tests should not touch the database, make network requests, or trigger WordPress hooks. If a test requires get_option() or wpdb, it is an integration test, not a unit test.
To write effective unit tests, you must leverage the patterns we established earlier, specifically Dependency Injection Basics. If your class manually instantiates its dependencies (e.g., new DatabaseService()), you cannot swap them for mocks during testing.
We manage our testing environment via Composer. While the WordPress testing suite is excellent for integration, we start with standard PHPUnit for pure logic.
Install PHPUnit:
Bashcomposer require --dev phpunit/phpunit
Configure phpunit.xml:
Create a phpunit.xml file in your project root to define your test suite:
XML<?xml version="1.0" encoding="UTF-8"?> style="color:#808080"><style="color:#4EC9B0">phpunit bootstrap="vendor/autoload.php" colors="true"> style="color:#808080"><style="color:#4EC9B0">testsuites> style="color:#808080"><style="color:#4EC9B0">testsuite name="Plugin Test Suite"> style="color:#808080"><style="color:#4EC9B0">directory>testsstyle="color:#808080"></style="color:#4EC9B0">directory> style="color:#808080"></style="color:#4EC9B0">testsuite> style="color:#808080"></style="color:#4EC9B0">testsuites> style="color:#808080"></style="color:#4EC9B0">phpunit>
Let's look at a concrete example within our Knowledge Base plugin. Suppose we have a LicenseValidator service that checks if a license key is active.
PHPnamespace KnowledgeBase\Services; class LicenseValidator { private $apiClient; public function __construct(ApiClient $apiClient) { $this->apiClient = $apiClient; } public function isKeyValid(string $key): bool { $response = $this->apiClient->get('/verify', ['key' => $key]); return isset($response['status']) && $response['status'] === 'active'; } }
Because we injected ApiClient via the constructor, we can easily swap it for a "mock" during testing. We don't want our unit test to actually hit a real API.
Using PHPUnit's built-in mocking framework, we can simulate the behavior of the ApiClient without performing network operations.
PHPnamespace KnowledgeBase\Tests; use PHPUnit\Framework\TestCase; use KnowledgeBase\Services\LicenseValidator; use KnowledgeBase\Services\ApiClient; class LicenseValidatorTest extends TestCase { public function testIsKeyValidReturnsTrueForActiveKey() { #6A9955">// 1. Create a mock for the ApiClient class $mockClient = $this->createMock(ApiClient::class); #6A9955">// 2. Define the expectation: the get method should be called once #6A9955">// with specific arguments and return a predefined array. $mockClient->expects($this->once()) ->method('get') ->with('/verify', ['key' => 'valid-key']) ->willReturn(['status' => 'active']); #6A9955">// 3. Inject the mock into the service $validator = new LicenseValidator($mockClient); #6A9955">// 4. Assert the result $this->assertTrue($validator->isKeyValid('valid-key')); } }
This approach allows you to test edge cases—like API timeouts or malformed JSON responses—simply by changing the willReturn() value of your mock. You can find more on this pattern in Unit Testing Foundations: Ensuring WordPress Plugin Stability.
Calculator service in your plugin that performs a complex calculation based on data retrieved from an injected DataProvider interface.DataProvider to return a specific set of numbers.Calculator produces the correct output based on those mocked inputs.phpunit.xml points to a bootstrap file that loads your autoloader. Without it, your tests won't be able to find your classes.Unit testing is the safety net that allows for aggressive refactoring. By using Dependency Injection to decouple your services, you enable the use of mocks, which makes testing fast and deterministic. Remember: if your test requires a database, it's not a unit test.
As we move forward, we will combine these unit tests with the WordPress testing suite to ensure that our database migrations and API endpoints function correctly in a real WordPress environment.
Up next: Integration Testing — we will leverage WP_UnitTestCase to test our database interactions and hook logic within a full WordPress lifecycle.
Learn how to use PHPUnit to test your WordPress REST API endpoints. We cover setting up WP_UnitTestCase, mocking requests, and verifying secure responses.
Read moreLearn how to set up a testing environment and write your first PHPUnit test to ensure your WordPress plugin remains stable during active development.
Unit Testing with PHPUnit
Custom Hooks for React