Mahamudul Hasan Rubel
HomeBlogCoursesAboutProjectsSkillsExperiencePhotosContact
Mahamudul Hasan Rubel

Senior Software Engineer crafting high-performance web applications and SaaS platforms.

Navigation

  • Home
  • Blog
  • Courses
  • About
  • Projects
  • Skills
  • Experience
  • Photos
  • Contact

Get in Touch

Available for senior/lead roles and consulting.

bd.mhrubel@gmail.comHire Me

Subscribe to the newsletter

Get new articles and course lessons delivered to your inbox. No spam, unsubscribe anytime.

© 2026 Mahamudul Hasan Rubel. All rights reserved.

Built with using Next.js 16 & Tailwind v4

Back to Blog
Lesson 27 of the Advanced WordPress Plugin Engineering: Scale, Security & React UIs course
WordPressJune 28, 20264 min read

Unit Testing with PHPUnit: A Guide for WordPress Engineers

Master unit testing with PHPUnit in WordPress. Learn to isolate your business logic, mock external dependencies, and build a robust testing suite.

PHPUnitTestingUnit TestingWordPressDevelopmentphpplugin-development

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.

The Philosophy of Unit Testing in WordPress

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.

Setting Up PHPUnit

We manage our testing environment via Composer. While the WordPress testing suite is excellent for integration, we start with standard PHPUnit for pure logic.

  1. Install PHPUnit:

    Bash
    composer require --dev phpunit/phpunit
  2. 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>

Writing Testable Service Classes

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.

PHP
namespace 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.

Mocking External Dependencies

Using PHPUnit's built-in mocking framework, we can simulate the behavior of the ApiClient without performing network operations.

PHP
namespace 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.

Hands-on Exercise

  1. Create a Calculator service in your plugin that performs a complex calculation based on data retrieved from an injected DataProvider interface.
  2. Implement a unit test that mocks the DataProvider to return a specific set of numbers.
  3. Assert that your Calculator produces the correct output based on those mocked inputs.

Common Pitfalls

  • Testing Private Methods: Never test private methods directly. If a private method is complex enough to require testing, it likely belongs in its own class as a public method.
  • Over-Mocking: If you find yourself mocking five different classes to test one method, your class is likely violating the Single Responsibility Principle. Refactor your code into smaller, more focused services.
  • Ignoring the Bootstrap: Ensure your phpunit.xml points to a bootstrap file that loads your autoloader. Without it, your tests won't be able to find your classes.
  • Mixing Unit and Integration Tests: Do not try to bootstrap the entire WordPress environment just to test a simple math function. Keep your unit tests fast and environment-agnostic.

Recap

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.

Previous lessonLinting and Code QualityNext lesson Integration Testing
Back to Blog

Similar Posts

WordPressJune 26, 20263 min read

Unit Testing API Endpoints in WordPress: A Practical Guide

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 more
WordPressJune 25, 20263 min read

Unit Testing Foundations: Ensuring WordPress Plugin Stability

Learn how to set up a testing environment and write your first PHPUnit test to ensure your WordPress plugin remains stable during active development.

Part of the course

Advanced WordPress Plugin Engineering: Scale, Security & React UIs

advanced · Lesson 27 of 56

  1. 1

    Modern PHP Standards for WordPress

    3 min
  2. 2

    Dependency Injection Basics

    3 min
  3. 3

    Architecting Service Providers

    3 min
Read more
WordPressJune 28, 20264 min read

Test-Driven Development Workflow: Building Robust WordPress Plugins

Master the Test-Driven Development workflow in WordPress. Learn to write failing tests, implement clean code, and refactor with confidence for your plugin.

Read more
  • 4

    Advanced Custom Database Tables

    4 min
  • 5

    Data Access Objects Pattern

    3 min
  • 6

    Query Caching Strategies

    4 min
  • 7

    Database Indexing for Scale

    4 min
  • 8

    Sanitization Pipelines

    3 min
  • 9

    Output Escaping Patterns

    4 min
  • 10

    Nonce Management Architecture

    3 min
  • 11

    Capability and Permission Systems

    3 min
  • 12

    Preventing SQL Injection

    4 min
  • 13

    Secure REST API Endpoints

    3 min
  • 14

    Cross-Site Scripting Mitigation

    4 min
  • 15

    Auditing Plugin Security

    4 min
  • 16

    Modern Build Tooling with Vite

    3 min
  • 17

    React Component Architecture

    3 min
  • 18

    State Management with @wordpress/data

    3 min
  • 19

    Block API v2 Essentials

    3 min
  • 20

    InnerBlocks and Nested Structures

    3 min
  • 21

    Custom REST API Integration

    3 min
  • 22

    Optimizing React Rendering

    4 min
  • 23

    Code Splitting and Lazy Loading

    4 min
  • 24

    Advanced Admin Dashboards

    4 min
  • 25

    Component Library Design

    3 min
  • 26

    Linting and Code Quality

    3 min
  • 27

    Unit Testing with PHPUnit

    4 min
  • 28

    Integration Testing

    3 min
  • 29

    Test-Driven Development Workflow

    4 min
  • 30

    Automated CI/CD Pipelines

    3 min
  • 31

    Versioning and Release Management

    3 min
  • 32

    Internationalization (i18n)

    3 min
  • 33

    Licensing Infrastructure

    4 min
  • 34

    Automated Update API

    3 min
  • 35

    Documentation Systems

    4 min
  • 36

    Refactoring for Distribution

    4 min
  • 37

    Plugin Lifecycle Management

    3 min
  • 38

    Performance Monitoring

    3 min
  • 39

    Advanced Error Handling

    4 min
  • 40

    User Feedback Loops

    3 min
  • 41

    Handling Plugin Conflicts

    4 min
  • 42

    Advanced Hook Management

    4 min
  • 43

    Database Schema Evolution

    3 min
  • 44

    High-Concurrency Data Handling

    4 min
  • 45

    Object-Relational Mapping (ORM) Lite

    3 min
  • 46

    Advanced Query Filters

    4 min
  • 47

    Secure File Handling

    3 min
  • 48

    Background Processing

    4 min
  • 49

    Transient Caching Patterns

    4 min
  • 50

    Advanced Nonce Security

    3 min
  • 51

    Multi-tenancy Considerations

    3 min
  • 52

    Custom Gutenberg Block Controls

    3 min
  • 53

    Block Transforms and Deprecation

    4 min
  • 54

    Dynamic Block Rendering

    4 min
  • 55

    Advanced State Persistence

    4 min
  • 56

    Custom Hooks for React

    Coming soon
  • View full course