doiftrue / unitest-wp-copy
Collection of WordPress core functions and classes that can be used in unit tests to simulate WordPress environment.
Requires
None
Requires (Dev)
None
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-08-13 04:22:29 UTC
README
Helper library for PHPUnit tests. It provides selected WordPress core functions and classes that can run without full WordPress bootstrap (database or external services).
Use it with WP_Mock. The runtime keeps real WordPress pure-PHP behavior, while WP_Mock lets tests replace functions marked as mockable when that is needed — which is almost always the case in unit tests.
Quick Start
-
Install the package line matching your WordPress version, plus WP_Mock:
composer require --dev doiftrue/unitest-wp-copy:6.9.* composer require --dev 10up/wp_mock -
Initialize both in the PHPUnit bootstrap. Unitest_WP_Copy must initialize first:
File:
tests/bootstrap.phprequire_once __DIR__ . '/../vendor/autoload.php'; \Unitest_WP_Copy\Bootstrap::init(); \WP_Mock::bootstrap();
Quick Example
Suppose your code turns a raw, user-submitted comment into safe HTML:
function render_comment( string $raw ): string { // wp_kses_post() strips disallowed tags. // make_clickable() linkifies URLs. // wpautop() adds paragraphs — all real WordPress logic. return wpautop( make_clickable( wp_kses_post( $raw ) ) ); }
The test uses real WordPress sanitizing and formatting behavior, but can still mock a supported boundary when necessary:
class RenderCommentTest extends \PHPUnit\Framework\TestCase { protected function setUp(): void { parent::setUp(); WP_Mock::setUp(); } protected function tearDown(): void { WP_Mock::tearDown(); parent::tearDown(); } public function test__renders_safe_html(): void { $html = render_comment( 'Great post! <script>alert(1)</script> visit https://example.com <b>thanks</b>' ); $this->assertStringNotContainsString( '<script>', $html ); // kses removed it $this->assertStringContainsString( '<a href="https://example.com"', $html ); // linkified $this->assertStringContainsString( '<b>thanks</b>', $html ); // allowed tag kept } public function test__mocks_a_supported_function(): void { WP_Mock::userFunction( 'is_multisite' )->andReturn( true ); $this->assertTrue( is_multisite() ); } }
Without WP_Mock
You may initialize only \Unitest_WP_Copy\Bootstrap::init() and use the real runtime. However, you will not be able to conveniently mock functions that the runtime has already loaded.
Available Symbols
For the full list of available classes/functions, see:
SYMBOLS-INFO.md. It separately lists symbols that are mockable via WP_Mock.
Runtime-Adapted Classes
Some WordPress classes cannot be copied as a whole, so the runtime provides a partial adapter instead. Such classes are listed in the first section of SYMBOLS-INFO.md together with their public methods and properties, where [wp] marks an unchanged copied WordPress method and [adapted] marks a runtime-specific implementation.
They are regular PHP classes, not WP_Mock symbols: use an instance directly, or extend it to build your own mock.
Currently available: \Unitest_WP_Copy\WPDB_Runtime — a non-querying wpdb adapter for SQL-building code. Bootstrap assigns an instance to the $wpdb global.
global $wpdb; $query = $wpdb->prepare( "SELECT * FROM {$wpdb->posts} WHERE post_title = %s", "O'Reilly" ); $this->assertSame( "SELECT * FROM wp_posts WHERE post_title = 'O\\'Reilly'", $wpdb->remove_placeholder_escape( $query ) );
Extend it when your code needs querying methods:
class My_WPDB extends \Unitest_WP_Copy\WPDB_Runtime { public array $results = []; public function get_results( $query = null, $output = OBJECT ) { return $this->results; } } $GLOBALS['wpdb'] = new My_WPDB();
Restore $GLOBALS['wpdb'] in tearDown() if a test replaces it.
Supported WordPress Lines
Use the package line that matches your WP version:
| WordPress line | Composer constraint |
|---|---|
| 7.0 | doiftrue/unitest-wp-copy:7.0.* |
| 6.9 | doiftrue/unitest-wp-copy:6.9.* |
| 6.8 | doiftrue/unitest-wp-copy:6.8.* |
| 6.7 | doiftrue/unitest-wp-copy:6.7.* |
| 6.6 | doiftrue/unitest-wp-copy:6.6.* |
| 6.5 | doiftrue/unitest-wp-copy:6.5.* |
Real release tags use 4 numbers, for example 7.0.2.8:
7.0is the target WordPress version line;2.8is this repository's version for that line.
Usage examples in your composer.json:
7.0.2.8- pin one exact release.~7.0.2.8- allow conservative updates starting from this build (usually small runtime fixes).7.0.*- allow any update in the WP7.0line (new copied functions/classes may appear and affect existing tests).
Bootstrap Overrides and Shared State
Define overrides before \Unitest_WP_Copy\Bootstrap::init().
// tests/bootstrap.php define( 'ABSPATH', '/srv/wp/' ); define( 'WP_CONTENT_DIR', '/srv/wp/wp-content' ); define( 'WP_CONTENT_URL', 'https://wp.test/wp-content' ); define( 'WP_ENVIRONMENT_TYPE', 'development' ); define( 'WP_DEBUG', true ); // Used by get_option() $GLOBALS['stub_wp_options'] = (object) [ 'home' => 'https://wp.test', 'siteurl' => 'https://wp.test', 'gmt_offset' => 0, 'timezone_string' => 'UTC', 'language' => 'en-US', 'blogdescription' => 'unitest-wp-copy runtime', 'admin_email' => 'admin@wp.test', 'stylesheet' => 'unitest-wp-copy', 'use_smilies' => true, 'use_balanceTags' => true, 'WPLANG' => '', 'blog_charset' => 'UTF-8', 'html_type' => 'text/html', 'thumbnail_size_w' => 150, 'thumbnail_size_h' => 150, 'thumbnail_crop' => true, 'medium_size_w' => 300, 'medium_size_h' => 300, 'medium_large_size_w' => 768, 'medium_large_size_h' => 0, 'large_size_w' => 1024, 'large_size_h' => 1024, ]; // Used by get_site_option() $GLOBALS['stub_wp_site_options'] = (object) [ 'site_name' => 'Test network', ]; require_once __DIR__ . '/vendor/autoload.php'; \Unitest_WP_Copy\Bootstrap::init(); \WP_Mock::bootstrap();
Redefine Runtime Globals
Runtime globals initialized or updated by bootstrap (shared in one PHP process):
$GLOBALS['stub_wp_options'] $GLOBALS['stub_wp_site_options'] $GLOBALS['timestart'] $_SERVER['HTTP_HOST'] $blog_id $wp_plugin_paths $shortcode_tags $wp_locale $wp_post_types $wp_taxonomies $wp_filter $wp_actions $wp_filters $wp_current_filter $allowedposttags $allowedtags $allowedentitynames $allowedxmlentitynames $wpsmiliestrans $wp_smiliessearch
If a test mutates these globals/options, restore them in setUp() / tearDown().
How get_option() Works
get_option() uses $GLOBALS['stub_wp_options'] instead of a database. Configured options have priority over WP_Mock handlers so that a broad mock cannot accidentally change options used by nested runtime calls.
The lookup order is:
pre_option_{$option}andpre_optionfilters;- the value in
$GLOBALS['stub_wp_options']and theoption_{$option}filter; - a
WP_Mock::userFunction( 'get_option', ... )handler for an option not present in the store; - the
default_option_{$option}filter and the default value.
Override a configured option by changing the store:
$GLOBALS['stub_wp_options']->medium_size_w = 640;
Use WP_Mock to mock an option that does not exist in $GLOBALS['stub_wp_options']:
WP_Mock::userFunction( 'get_option', [ 'args' => [ 'my_plugin_option', false ], 'return' => 'test-value', ] );
IMPORTANT: WP_Mock cannot override an option when it exists in $GLOBALS['stub_wp_options'].
Redefine Constants
Constants you can predefine before bootstrap:
ABSPATH WPINC WP_CONTENT_DIR WP_CONTENT_URL WP_ENVIRONMENT_TYPE WP_START_TIMESTAMP WP_MEMORY_LIMIT WP_MAX_MEMORY_LIMIT WP_DEVELOPMENT_MODE WP_DEBUG WP_DEBUG_DISPLAY WP_DEBUG_LOG WP_CACHE SCRIPT_DEBUG MEDIA_TRASH SHORTINIT WP_PLUGIN_DIR WP_PLUGIN_URL PLUGINDIR WPMU_PLUGIN_DIR WPMU_PLUGIN_URL MUPLUGINDIR COOKIEHASH USER_COOKIE PASS_COOKIE AUTH_COOKIE SECURE_AUTH_COOKIE LOGGED_IN_COOKIE TEST_COOKIE COOKIEPATH SITECOOKIEPATH ADMIN_COOKIE_PATH PLUGINS_COOKIE_PATH COOKIE_DOMAIN RECOVERY_MODE_COOKIE FORCE_SSL_ADMIN AUTOSAVE_INTERVAL EMPTY_TRASH_DAYS WP_POST_REVISIONS WP_CRON_LOCK_TIMEOUT CUSTOM_TAGS
Redefine Functions
Copied functions are wrapped with if ( ! function_exists( '...' ) ), so you can override specific functions by defining them before bootstrap init.
When to Use It
Use it when:
- you need real behavior of selected WP functions/classes in plain PHPUnit;
- your tested code mostly depends on WP pure-PHP logic.
Do not use it when:
- you need a full WordPress runtime and bootstrap;
- your test mostly depends on real DB/network/filesystem-heavy WP behavior.
Instructions for AI Agents
Add the following to the testing section of your project's AGENTS.md:
### Tests Runtime This project uses `doiftrue/unitest-wp-copy` with `WP_Mock` for PHPUnit tests. Before writing or changing tests: 1. Read `vendor/doiftrue/unitest-wp-copy/README.md` to understand the test runtime. 2. Check `vendor/doiftrue/unitest-wp-copy/SYMBOLS-INFO.md` for the WordPress functions and classes available in the runtime. Its first section lists runtime-adapted classes (like `\Unitest_WP_Copy\WPDB_Runtime`) with their public methods — use or extend them instead of WP_Mock. 3. Use `WP_Mock` when a runtime function listed as mockable needs to be mocked.