Introduction to WordPress Testing
Testing WordPress applications presents unique challenges due to WordPress's architecture as a content management system with a plugin ecosystem. However, with the right tools and approaches, you can ensure your WordPress themes, plugins, and customizations work reliably.
Real-world analogy: Testing WordPress is like quality control for a modular furniture system. Each piece (plugin, theme) needs to work on its own, but also integrate perfectly with the core system and other components to create a complete, functional product.
WordPress Testing Ecosystem
WordPress Core Test Suite
WordPress includes its own test suite, built on top of PHPUnit, that provides tools for testing WordPress-specific functionality. This includes fixtures, factories, and assertions designed for WordPress development.
Key Testing Components
- WP_UnitTestCase - Base class for WordPress unit tests
- WP_Ajax_UnitTestCase - For testing AJAX functionality
- WP_Test_REST_TestCase - For testing REST API endpoints
- WP_Test_REST_Controller_Testcase - For testing REST API controllers
Testing Tools
- WP-CLI - Command-line interface for WordPress, useful for setting up test environments
- WP Browser - Extension of Codeception for WordPress-specific acceptance testing
- Cypress - End-to-end testing framework that can be used with WordPress
- Brain Monkey - Library for mocking WordPress functions and hooks
Real-world example: The Yoast SEO plugin, which is installed on millions of WordPress sites, has an extensive test suite ensuring that their plugin works correctly with different WordPress versions and configurations.
Setting Up WordPress Testing Environment
Using WP-CLI to Set Up Tests
// Install WP-CLI
curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
chmod +x wp-cli.phar
sudo mv wp-cli.phar /usr/local/bin/wp
// For a plugin
cd wp-content/plugins/my-plugin
wp scaffold plugin-tests my-plugin
// For a theme
cd wp-content/themes/my-theme
wp scaffold theme-tests my-theme
// Setup the test environment
bash bin/install-wp-tests.sh wordpress_test root password localhost latest
Manual Setup for Plugin Testing
// Clone WordPress test suite
svn co https://develop.svn.wordpress.org/trunk/tests/phpunit/includes/ /tmp/wordpress-tests-lib
// Create phpunit.xml configuration
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
bootstrap="tests/bootstrap.php"
backupGlobals="false"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
>
<testsuites>
<testsuite name="My Plugin Test Suite">
<directory suffix=".php">./tests/</directory>
</testsuite>
</testsuites>
</phpunit>
// Create bootstrap.php
<?php
// Path to the WordPress codebase
$GLOBALS['wp_tests_options'] = array(
'active_plugins' => array( 'my-plugin/my-plugin.php' ),
);
require_once dirname( dirname( __FILE__ ) ) . '/vendor/autoload.php';
// WP test environment
require_once '/tmp/wordpress-tests-lib/includes/bootstrap.php';
Writing WordPress Unit Tests
WordPress unit tests extend PHPUnit's functionality with WordPress-specific tools and fixtures.
Basic WordPress Test Structure
// tests/test-sample.php
class SampleTest extends WP_UnitTestCase {
public function test_sample() {
// Replace this with an actual test
$this->assertTrue( true );
}
public function test_wp_functionality() {
// Create a post
$post_id = $this->factory->post->create([
'post_title' => 'Test Post',
'post_content' => 'Test content',
'post_status' => 'publish'
]);
// Verify the post was created
$post = get_post($post_id);
$this->assertEquals('Test Post', $post->post_title);
}
}
Using WordPress Test Factories
WordPress test factories make it easy to create test data:
// Create a user
$user_id = $this->factory->user->create([
'role' => 'editor',
'user_login' => 'test_editor',
'user_email' => 'editor@example.com'
]);
// Create multiple posts
$post_ids = $this->factory->post->create_many(5, [
'post_author' => $user_id
]);
// Create a term
$term_id = $this->factory->term->create([
'taxonomy' => 'category',
'name' => 'Test Category'
]);
// Create a comment
$comment_id = $this->factory->comment->create([
'comment_post_ID' => $post_ids[0],
'comment_content' => 'Test comment',
'user_id' => $user_id
]);
Testing WordPress Hooks
public function test_plugin_adds_filter() {
// Setup
$plugin = new My_Plugin();
$plugin->init();
// Verify filter is registered
$this->assertNotFalse( has_filter('the_content', [$plugin, 'filter_content']) );
}
public function test_filter_content() {
// Setup
$plugin = new My_Plugin();
$content = 'Original content';
// Apply filter directly
$filtered_content = $plugin->filter_content($content);
// Assertions
$this->assertStringContainsString('Modified', $filtered_content);
}
Real-world example: The WooCommerce plugin, which powers millions of e-commerce stores, has over 5,000 unit tests ensuring that critical functionality like checkout processes, product management, and payment gateways all work reliably.
Testing WordPress Plugins
Plugins extend WordPress functionality and should have comprehensive tests to ensure reliability.
Testing Plugin Activation
public function test_plugin_activation() {
// Verify plugin is active
$this->assertTrue( is_plugin_active('my-plugin/my-plugin.php') );
// Test activation setup
$option = get_option('my_plugin_option');
$this->assertNotFalse($option);
$this->assertEquals('default_value', $option['setting']);
}
Testing Plugin Admin Pages
public function test_admin_menu_added() {
// Set current user as admin
$user_id = $this->factory->user->create(['role' => 'administrator']);
wp_set_current_user($user_id);
// Initialize plugin
$plugin = new My_Plugin();
$plugin->admin_init();
// Manually call admin menu hook
do_action('admin_menu');
global $submenu;
$this->assertArrayHasKey('options-general.php', $submenu);
// Find our submenu item
$our_submenu = false;
foreach ($submenu['options-general.php'] as $item) {
if ($item[2] === 'my-plugin-settings') {
$our_submenu = true;
break;
}
}
$this->assertTrue($our_submenu);
}
Testing Plugin Settings
public function test_register_settings() {
$plugin = new My_Plugin();
$plugin->init();
// Call the initialization action manually
do_action('admin_init');
// Verify setting is registered
$settings = get_registered_settings();
$this->assertArrayHasKey('my_plugin_option', $settings);
}
public function test_setting_validation() {
$plugin = new My_Plugin_Settings();
// Valid input
$valid_input = [
'text_field' => 'Valid text',
'number_field' => '42'
];
$validated = $plugin->validate_settings($valid_input);
$this->assertEquals($valid_input, $validated);
// Invalid input
$invalid_input = [
'text_field' => 'Valid text',
'number_field' => 'not a number'
];
$validated = $plugin->validate_settings($invalid_input);
$this->assertEquals('0', $validated['number_field']);
}
Testing Plugin Shortcodes
public function test_shortcode_registration() {
$plugin = new My_Plugin();
$plugin->init();
// Verify shortcode is registered
$this->assertTrue(shortcode_exists('my_shortcode'));
}
public function test_shortcode_output() {
$plugin = new My_Plugin();
// Test with default attributes
$output = $plugin->shortcode_handler([]);
$this->assertStringContainsString('default-class', $output);
// Test with custom attributes
$output = $plugin->shortcode_handler([
'class' => 'custom-class',
'title' => 'Custom Title'
]);
$this->assertStringContainsString('custom-class', $output);
$this->assertStringContainsString('Custom Title', $output);
}
Testing WordPress Themes
Testing themes involves checking template rendering, custom template tags, and theme functionality.
Testing Theme Setup
public function test_theme_support() {
// Ensure theme is active
switch_theme('my-theme');
// Call theme setup function
my_theme_setup();
// Verify theme supports are added
$this->assertTrue(current_theme_supports('post-thumbnails'));
$this->assertTrue(current_theme_supports('custom-logo'));
$this->assertTrue(current_theme_supports('menus'));
}
public function test_widget_registration() {
// Call widget registration function
my_theme_widgets_init();
// Verify sidebar is registered
$sidebars = $GLOBALS['wp_registered_sidebars'];
$this->assertArrayHasKey('sidebar-1', $sidebars);
}
Testing Template Tags
public function test_posted_on() {
// Create a post
$post_id = $this->factory->post->create([
'post_date' => '2023-01-15 12:00:00'
]);
// Load post data
global $post;
$post = get_post($post_id);
setup_postdata($post);
// Capture output of template tag
ob_start();
my_theme_posted_on();
$output = ob_get_clean();
// Verify output
$this->assertStringContainsString('January 15, 2023', $output);
}
Testing Custom Template Parts
public function test_content_template_part() {
// Create a post
$post_id = $this->factory->post->create([
'post_title' => 'Test Post',
'post_content' => 'Test content',
'post_type' => 'post'
]);
// Setup global post
global $post;
$post = get_post($post_id);
setup_postdata($post);
// Capture output
ob_start();
get_template_part('template-parts/content', get_post_type());
$output = ob_get_clean();
// Verify output
$this->assertStringContainsString('Test Post', $output);
$this->assertStringContainsString('Test content', $output);
}
Real-world example: The Twenty Twenty-Three theme (a WordPress default theme) includes tests that verify proper rendering across different post types, ensure responsive design functions correctly, and confirm that theme customization options work as expected.
Testing WordPress REST API
WordPress provides a robust REST API that can be extended by plugins and themes. Testing these extensions is critical.
Setting Up REST API Tests
class REST_API_Test extends WP_Test_REST_TestCase {
protected $server;
public function setUp(): void {
parent::setUp();
// Initialize REST server
global $wp_rest_server;
$this->server = $wp_rest_server = new WP_REST_Server;
do_action('rest_api_init');
}
public function tearDown(): void {
global $wp_rest_server;
$wp_rest_server = null;
parent::tearDown();
}
}
Testing REST API Routes
public function test_register_route() {
$routes = $this->server->get_routes();
$this->assertArrayHasKey('/my-plugin/v1/items', $routes);
}
public function test_get_items() {
// Create test items
$item_ids = $this->factory->post->create_many(5, [
'post_type' => 'my_custom_type'
]);
// Make REST API request
$request = new WP_REST_Request('GET', '/my-plugin/v1/items');
$response = $this->server->dispatch($request);
// Check response
$this->assertEquals(200, $response->get_status());
$data = $response->get_data();
$this->assertCount(5, $data);
}
Testing REST API Authentication
public function test_create_item_unauthorized() {
// Create request without authentication
$request = new WP_REST_Request('POST', '/my-plugin/v1/items');
$request->set_body_params([
'title' => 'New Item',
'content' => 'Item content'
]);
// Dispatch request
$response = $this->server->dispatch($request);
// Should return 401 Unauthorized
$this->assertEquals(401, $response->get_status());
}
public function test_create_item_authorized() {
// Create and authenticate as admin user
$user_id = $this->factory->user->create(['role' => 'administrator']);
wp_set_current_user($user_id);
// Create request
$request = new WP_REST_Request('POST', '/my-plugin/v1/items');
$request->set_body_params([
'title' => 'New Item',
'content' => 'Item content'
]);
// Dispatch request
$response = $this->server->dispatch($request);
// Should return 201 Created
$this->assertEquals(201, $response->get_status());
$data = $response->get_data();
$this->assertEquals('New Item', $data['title']['rendered']);
}
Testing Custom Blocks (Gutenberg)
With the WordPress block editor (Gutenberg), testing custom blocks is essential for theme and plugin developers.
Testing Block Registration
public function test_block_registration() {
// Initialize plugin that registers blocks
$plugin = new My_Blocks_Plugin();
$plugin->init();
// Manually trigger blocks registration
do_action('init');
// Check if block is registered
$registry = WP_Block_Type_Registry::get_instance();
$this->assertTrue($registry->is_registered('my-plugin/custom-block'));
}
Testing Server-Side Rendering
public function test_block_render() {
// Register block
register_block_type('my-plugin/custom-block', [
'render_callback' => [$this, 'render_custom_block'],
'attributes' => [
'title' => [
'type' => 'string',
'default' => ''
],
'content' => [
'type' => 'string',
'default' => ''
]
]
]);
// Test rendering with attributes
$attributes = [
'title' => 'Test Title',
'content' => 'Test Content'
];
$html = render_block([
'blockName' => 'my-plugin/custom-block',
'attrs' => $attributes
]);
// Verify output
$this->assertStringContainsString('Test Title', $html);
$this->assertStringContainsString('Test Content', $html);
}
JavaScript Testing for Blocks
For client-side block functionality, use JavaScript testing tools like Jest:
// In package.json
{
"scripts": {
"test": "wp-scripts test-unit-js"
}
}
// Example block component test
// __tests__/components/MyBlock.test.js
import { render, screen } from '@testing-library/react';
import MyBlock from '../../src/components/MyBlock';
describe('MyBlock', () => {
test('renders with default props', () => {
render( );
expect(screen.getByText('Default Title')).toBeInTheDocument();
});
test('renders with custom props', () => {
render( );
expect(screen.getByText('Custom Title')).toBeInTheDocument();
});
});
Real-world example: Automattic's Jetpack plugin, which includes dozens of custom blocks, has an extensive test suite for its Gutenberg blocks that verifies both PHP server-side rendering and JavaScript block behavior.
Integration Testing in WordPress
Integration tests verify that different parts of your WordPress application work together correctly.
Testing Plugin Interactions
public function test_plugin_interaction() {
// Activate both plugins
activate_plugin('my-plugin/my-plugin.php');
activate_plugin('another-plugin/another-plugin.php');
// Initialize plugins
$my_plugin = new My_Plugin();
$another_plugin = new Another_Plugin();
$my_plugin->init();
$another_plugin->init();
// Test interaction
$result = apply_filters('another_plugin_filter', 'original');
$this->assertEquals('modified by my-plugin', $result);
}
Testing with Real Database
class Database_Integration_Test extends WP_UnitTestCase {
public function test_saving_custom_post_type() {
// Register custom post type
register_post_type('product', [
'public' => true
]);
// Create post
$post_id = wp_insert_post([
'post_title' => 'Test Product',
'post_type' => 'product',
'post_status' => 'publish'
]);
// Add custom metadata
update_post_meta($post_id, '_price', 99.99);
// Test custom query
$query = new WP_Query([
'post_type' => 'product',
'meta_query' => [
[
'key' => '_price',
'value' => 50,
'compare' => '>',
'type' => 'NUMERIC'
]
]
]);
$this->assertEquals(1, $query->post_count);
$this->assertEquals($post_id, $query->posts[0]->ID);
}
}
Testing AJAX Functionality
class AJAX_Test extends WP_Ajax_UnitTestCase {
public function test_ajax_action() {
// Prepare request
$_POST['action'] = 'my_plugin_action';
$_POST['nonce'] = wp_create_nonce('my_plugin_nonce');
$_POST['data'] = 'test data';
// Set current user
$user_id = $this->factory->user->create(['role' => 'editor']);
wp_set_current_user($user_id);
// Spoof admin screen
set_current_screen('dashboard');
// Prepare to catch output
try {
$this->_handleAjax('my_plugin_action');
} catch (WPAjaxDieContinueException $e) {
// We expected this exception
}
// Get response
$response = json_decode($this->_last_response);
// Verify response
$this->assertTrue($response->success);
$this->assertEquals('processed', $response->data->status);
}
}
Functional Testing with WP Browser
WP Browser extends Codeception to provide WordPress-specific testing functionality.
Setting Up WP Browser
// Install via Composer
composer require --dev lucatume/wp-browser
// Initialize Codeception
vendor/bin/codecept init
// Configure WPBrowser module in tests/acceptance.suite.yml
modules:
enabled:
- WPBrowser:
url: 'http://localhost/wordpress'
adminUsername: 'admin'
adminPassword: 'password'
adminPath: '/wp-admin'
Writing Acceptance Tests
// tests/acceptance/AdminLoginCest.php
class AdminLoginCest
{
public function _before(AcceptanceTester $I)
{
// Clear cookies
}
public function loginAsAdmin(AcceptanceTester $I)
{
$I->amOnPage('/wp-login.php');
$I->fillField('Username', 'admin');
$I->fillField('Password', 'password');
$I->click('Log In');
$I->see('Dashboard');
}
public function checkPluginSettings(AcceptanceTester $I)
{
$I->loginAsAdmin();
$I->amOnAdminPage('options-general.php?page=my-plugin');
$I->see('My Plugin Settings');
$I->fillField('my_plugin_option[text_field]', 'New Value');
$I->click('Save Changes');
$I->see('Settings saved.');
// Verify setting was saved
$I->seeInField('my_plugin_option[text_field]', 'New Value');
}
}
WPLoader for Integration Tests
// Configure WPLoader in tests/wpunit.suite.yml
modules:
enabled:
- WPLoader:
loadOnly: true
wpRootFolder: '/var/www/html/wordpress'
dbName: 'wordpress_test'
dbUser: 'root'
dbPassword: 'password'
tablePrefix: 'wp_'
domain: 'example.test'
plugins:
- 'my-plugin/my-plugin.php'
activatePlugins:
- 'my-plugin/my-plugin.php'
// Example WPLoader test
class MyPluginWPTest extends \Codeception\TestCase\WPTestCase
{
public function test_plugin_activated()
{
$this->assertTrue(is_plugin_active('my-plugin/my-plugin.php'));
// Test plugin functionality
$result = apply_filters('my_plugin_filter', 'default');
$this->assertEquals('modified', $result);
}
}
Real-world example: The WooCommerce team uses WP Browser to test critical user flows like checkout processes, product management, and admin interactions, ensuring that these complex processes work smoothly across different WordPress configurations.
Testing WordPress Multisite
WordPress Multisite adds another layer of complexity to testing, but the WordPress test suite provides tools for handling it.
Setting Up Multisite Tests
// In phpunit.xml.dist
<php>
<const name="WP_TESTS_MULTISITE" value="1" />
</php>
// Installing test environment for multisite
bash bin/install-wp-tests.sh wordpress_test root password localhost latest true
Testing Multisite-Specific Functionality
class Multisite_Test extends WP_UnitTestCase {
protected $plugin;
public function setUp(): void {
parent::setUp();
$this->plugin = new My_Multisite_Plugin();
$this->plugin->init();
}
public function test_network_activation() {
if (!is_multisite()) {
$this->markTestSkipped('Multisite not enabled');
}
// Create main site
$blog_id = $this->factory->blog->create();
// Switch to the blog
switch_to_blog($blog_id);
// Check if network-activated plugin sets up correctly
$option = get_option('my_plugin_option');
$this->assertNotFalse($option);
// Restore original blog
restore_current_blog();
}
public function test_network_admin_page() {
if (!is_multisite()) {
$this->markTestSkipped('Multisite not enabled');
}
// Set current user as super admin
$user_id = $this->factory->user->create();
grant_super_admin($user_id);
wp_set_current_user($user_id);
// Trigger network admin menu hook
do_action('network_admin_menu');
global $submenu;
$network_page_exists = false;
if (isset($submenu['settings.php'])) {
foreach ($submenu['settings.php'] as $item) {
if ($item[2] === 'my-plugin-network') {
$network_page_exists = true;
break;
}
}
}
$this->assertTrue($network_page_exists);
}
}
Mocking WordPress Functions
Testing WordPress code often requires mocking core functions. The Brain Monkey library helps with this.
Setting Up Brain Monkey
// Install via Composer
composer require --dev brain/monkey
// In bootstrap.php
require_once __DIR__ . '/../vendor/autoload.php';
// Set up Brain Monkey
Brain\Monkey\setUp();
// Add teardown hook
add_action('teardown', function() {
Brain\Monkey\tearDown();
});
Mocking WordPress Functions
use Brain\Monkey\Functions;
class Plugin_Test extends \PHPUnit\Framework\TestCase {
protected function setUp(): void {
parent::setUp();
Brain\Monkey\setUp();
}
protected function tearDown(): void {
Brain\Monkey\tearDown();
parent::tearDown();
}
public function test_plugin_uses_wp_functions() {
// Mock WordPress functions
Functions\expect('add_action')
->once()
->with('init', \Mockery::type('callable'));
Functions\expect('wp_enqueue_script')
->once()
->with('my-plugin-script', \Mockery::type('string'), \Mockery::type('array'), \Mockery::type('string'), true);
Functions\expect('get_option')
->once()
->with('my_plugin_option')
->andReturn(['setting' => 'value']);
// Call function that uses WordPress functions
$plugin = new My_Plugin();
$plugin->init();
// Brain Monkey automatically verifies expectations
}
}
Mocking WordPress Hooks
use Brain\Monkey\Actions;
use Brain\Monkey\Filters;
public function test_plugin_hooks() {
// Expect action to be added
Actions\expectAdded('init')
->once()
->with(\Mockery::type('callable'));
// Expect filter to be added
Filters\expectAdded('the_content')
->once()
->with(\Mockery::type('callable'));
// Initialize plugin
$plugin = new My_Plugin();
$plugin->init();
}
public function test_filter_applied() {
// Mock filter execution
Filters\expectApplied('my_plugin_filter')
->once()
->with('input')
->andReturn('filtered');
// Call function that applies filter
$result = apply_filter_to_input('input');
// Verify result
$this->assertEquals('filtered', $result);
}
Real-world example: The Pods Framework, which extends WordPress with custom content types, uses Brain Monkey to isolate its classes from WordPress core functions during unit testing, making tests faster and more focused.
Testing Database Queries
Testing WordPress database operations requires special approaches due to WordPress's global state.
Using the Test Database
class Database_Test extends WP_UnitTestCase {
public function test_custom_query() {
global $wpdb;
// Insert test data
$wpdb->insert(
$wpdb->posts,
[
'post_title' => 'Test Post',
'post_content' => 'Test content',
'post_status' => 'publish',
'post_type' => 'post'
]
);
$post_id = $wpdb->insert_id;
// Run custom query
$query = "SELECT ID, post_title FROM {$wpdb->posts} WHERE post_title = %s";
$results = $wpdb->get_results($wpdb->prepare($query, 'Test Post'));
// Verify results
$this->assertCount(1, $results);
$this->assertEquals($post_id, $results[0]->ID);
}
}
Testing Custom Database Tables
class Custom_Table_Test extends WP_UnitTestCase {
protected $table_name;
public function setUp(): void {
parent::setUp();
global $wpdb;
// Create test table
$this->table_name = $wpdb->prefix . 'my_plugin_data';
$charset_collate = $wpdb->get_charset_collate();
$sql = "CREATE TABLE {$this->table_name} (
id bigint(20) NOT NULL AUTO_INCREMENT,
name varchar(255) NOT NULL,
value longtext NOT NULL,
created_at datetime NOT NULL,
PRIMARY KEY (id)
) $charset_collate;";
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
dbDelta($sql);
}
public function tearDown(): void {
global $wpdb;
// Drop test table
$wpdb->query("DROP TABLE IF EXISTS {$this->table_name}");
parent::tearDown();
}
public function test_insert_and_query() {
global $wpdb;
// Insert test data
$wpdb->insert(
$this->table_name,
[
'name' => 'test_setting',
'value' => 'test_value',
'created_at' => current_time('mysql')
]
);
// Query data
$result = $wpdb->get_row(
$wpdb->prepare(
"SELECT * FROM {$this->table_name} WHERE name = %s",
'test_setting'
)
);
// Verify result
$this->assertNotNull($result);
$this->assertEquals('test_value', $result->value);
}
}
Practical Exercise
Exercise: Testing a WordPress Widget
Let's practice by writing tests for a custom WordPress widget:
// includes/class-recent-posts-widget.php
class My_Recent_Posts_Widget extends WP_Widget {
public function __construct() {
parent::__construct(
'my_recent_posts',
'My Recent Posts',
['description' => 'A widget that displays recent posts with thumbnails']
);
}
public function widget($args, $instance) {
$title = ! empty($instance['title']) ? $instance['title'] : 'Recent Posts';
$num_posts = ! empty($instance['num_posts']) ? absint($instance['num_posts']) : 5;
$show_thumbnail = isset($instance['show_thumbnail']) ? (bool)$instance['show_thumbnail'] : true;
$show_date = isset($instance['show_date']) ? (bool)$instance['show_date'] : true;
echo $args['before_widget'];
echo $args['before_title'] . apply_filters('widget_title', $title) . $args['after_title'];
$query_args = [
'posts_per_page' => $num_posts,
'post_status' => 'publish',
'order' => 'DESC',
'orderby' => 'date'
];
$posts = get_posts($query_args);
if ($posts) {
echo '';
} else {
echo 'No posts found.
';
}
echo $args['after_widget'];
}
public function form($instance) {
$title = ! empty($instance['title']) ? $instance['title'] : 'Recent Posts';
$num_posts = ! empty($instance['num_posts']) ? absint($instance['num_posts']) : 5;
$show_thumbnail = isset($instance['show_thumbnail']) ? (bool)$instance['show_thumbnail'] : true;
$show_date = isset($instance['show_date']) ? (bool)$instance['show_date'] : true;
?>
>
>
Your task:
- Write tests for the widget registration and initialization
- Test the widget rendering with different settings
- Test the form rendering and settings update
- Use WordPress test factories to create test posts
Summary
- WordPress testing requires specific approaches due to its architecture
- The WordPress test suite extends PHPUnit for WordPress-specific testing
- WP_UnitTestCase provides tools for testing WordPress functionality
- WordPress test factories make it easy to create test data
- Brain Monkey helps with mocking WordPress functions and hooks
- WP Browser enables acceptance testing for WordPress applications
- Testing themes, plugins, blocks, and APIs requires different approaches
Remember: While WordPress testing has its challenges, the investment in a good test suite pays off through more reliable plugins, themes, and websites.
Assignment
Create a complete test suite for a custom WordPress plugin with the following features:
- Develop a "Product Showcase" plugin with these components:
- Custom post type for Products
- Custom taxonomy for Product Categories
- Custom meta box for Product Details (price, SKU, etc.)
- Shortcode to display products
- Widget to display featured products
- Settings page with options for display
- REST API endpoint for products
- Write a comprehensive test suite that includes:
- Unit tests for all plugin components
- Tests for admin functionality
- Tests for shortcode rendering
- Tests for the widget
- Tests for the REST API endpoints
- Tests for settings validation and storage
- Include documentation on how to set up and run the tests
Bonus challenge: Add integration tests using WP Browser or Cypress to test the entire plugin from a user's perspective.