@silverassist/agents-toolkit 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +135 -0
- package/README.md +388 -0
- package/bin/cli.js +940 -0
- package/package.json +52 -0
- package/src/index.js +58 -0
- package/templates/agents/AGENTS.codex.md +195 -0
- package/templates/agents/AGENTS.md +195 -0
- package/templates/agents/CLAUDE.md +213 -0
- package/templates/agents/copilot-instructions.md +83 -0
- package/templates/shared/instructions/css-styling.instructions.md +142 -0
- package/templates/shared/instructions/documentation-language.instructions.md +216 -0
- package/templates/shared/instructions/github-workflow.instructions.md +245 -0
- package/templates/shared/instructions/php-standards.instructions.md +293 -0
- package/templates/shared/instructions/react-components.instructions.md +196 -0
- package/templates/shared/instructions/server-actions.instructions.md +429 -0
- package/templates/shared/instructions/testing-standards.instructions.md +264 -0
- package/templates/shared/instructions/tests.instructions.md +103 -0
- package/templates/shared/instructions/typescript.instructions.md +106 -0
- package/templates/shared/instructions/wordpress-plugin-architecture.instructions.md +238 -0
- package/templates/shared/prompts/README.md +129 -0
- package/templates/shared/prompts/_partials/README.md +57 -0
- package/templates/shared/prompts/_partials/documentation.md +203 -0
- package/templates/shared/prompts/_partials/git-operations.md +171 -0
- package/templates/shared/prompts/_partials/github-integration.md +100 -0
- package/templates/shared/prompts/_partials/jira-integration.md +155 -0
- package/templates/shared/prompts/_partials/pr-template.md +216 -0
- package/templates/shared/prompts/_partials/validations.md +87 -0
- package/templates/shared/prompts/add-tests.prompt.md +151 -0
- package/templates/shared/prompts/analyze-github-issue.prompt.md +73 -0
- package/templates/shared/prompts/analyze-ticket.prompt.md +63 -0
- package/templates/shared/prompts/create-plan.prompt.md +118 -0
- package/templates/shared/prompts/create-pr.prompt.md +139 -0
- package/templates/shared/prompts/finalize-pr.prompt.md +150 -0
- package/templates/shared/prompts/fix-issues.prompt.md +92 -0
- package/templates/shared/prompts/new-wp-component.prompt.md +51 -0
- package/templates/shared/prompts/new-wp-plugin.prompt.md +46 -0
- package/templates/shared/prompts/prepare-pr.prompt.md +123 -0
- package/templates/shared/prompts/prepare-release.prompt.md +74 -0
- package/templates/shared/prompts/quality-check.prompt.md +45 -0
- package/templates/shared/prompts/review-code.prompt.md +81 -0
- package/templates/shared/prompts/work-github-issue.prompt.md +96 -0
- package/templates/shared/prompts/work-ticket.prompt.md +98 -0
- package/templates/shared/skills/README.md +53 -0
- package/templates/shared/skills/component-architecture/SKILL.md +321 -0
- package/templates/shared/skills/create-component/SKILL.md +714 -0
- package/templates/shared/skills/domain-driven-design/SKILL.md +277 -0
- package/templates/shared/skills/plugin-creation/SKILL.md +1094 -0
- package/templates/shared/skills/quality-checks/SKILL.md +493 -0
- package/templates/shared/skills/release-management/SKILL.md +649 -0
- package/templates/shared/skills/testing/SKILL.md +682 -0
- package/templates/shared/skills/testing-patterns/SKILL.md +243 -0
|
@@ -0,0 +1,682 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: testing
|
|
3
|
+
description: Write and run PHPUnit tests for Silver Assist WordPress plugins. Covers WordPress Test Suite integration, test bootstrap, base TestCase class, factory patterns, database schema handling, test naming conventions, and common testing patterns. Use when writing new tests, debugging test failures, or setting up test infrastructure.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Silver Assist — Testing
|
|
7
|
+
|
|
8
|
+
This skill covers the testing strategy for Silver Assist WordPress plugins, including PHPUnit configuration, WordPress Test Suite integration, test patterns, and best practices.
|
|
9
|
+
|
|
10
|
+
## When to Use
|
|
11
|
+
|
|
12
|
+
- Writing new unit or integration tests
|
|
13
|
+
- Setting up test infrastructure for a new plugin
|
|
14
|
+
- Debugging test failures
|
|
15
|
+
- Understanding WordPress Test Suite patterns
|
|
16
|
+
- Dealing with database operations in tests
|
|
17
|
+
- Running tests locally or in CI
|
|
18
|
+
|
|
19
|
+
## Prerequisites
|
|
20
|
+
|
|
21
|
+
- `composer install` completed
|
|
22
|
+
- For integration tests: WordPress Test Suite installed
|
|
23
|
+
- PHPUnit 9.6+ (via Composer)
|
|
24
|
+
- yoast/phpunit-polyfills (via Composer)
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Test Infrastructure Setup
|
|
29
|
+
|
|
30
|
+
### PHPUnit Configuration
|
|
31
|
+
|
|
32
|
+
File: `phpunit.xml.dist`
|
|
33
|
+
|
|
34
|
+
```xml
|
|
35
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
36
|
+
<phpunit
|
|
37
|
+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
38
|
+
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.6/phpunit.xsd"
|
|
39
|
+
bootstrap="tests/bootstrap.php"
|
|
40
|
+
colors="true"
|
|
41
|
+
verbose="true"
|
|
42
|
+
stopOnFailure="false"
|
|
43
|
+
stopOnError="false"
|
|
44
|
+
beStrictAboutOutputDuringTests="true"
|
|
45
|
+
beStrictAboutTodoAnnotatedTests="true">
|
|
46
|
+
|
|
47
|
+
<testsuites>
|
|
48
|
+
<testsuite name="unit">
|
|
49
|
+
<directory suffix="Test.php">./tests/Unit</directory>
|
|
50
|
+
</testsuite>
|
|
51
|
+
<testsuite name="integration">
|
|
52
|
+
<directory suffix="Test.php">./tests/Integration</directory>
|
|
53
|
+
</testsuite>
|
|
54
|
+
<testsuite name="all">
|
|
55
|
+
<directory suffix="Test.php">./tests/Unit</directory>
|
|
56
|
+
<directory suffix="Test.php">./tests/Integration</directory>
|
|
57
|
+
</testsuite>
|
|
58
|
+
</testsuites>
|
|
59
|
+
|
|
60
|
+
<groups>
|
|
61
|
+
<exclude>
|
|
62
|
+
<group>ajax</group>
|
|
63
|
+
<group>ms-files</group>
|
|
64
|
+
<group>external-http</group>
|
|
65
|
+
</exclude>
|
|
66
|
+
</groups>
|
|
67
|
+
|
|
68
|
+
<coverage processUncoveredFiles="true">
|
|
69
|
+
<include>
|
|
70
|
+
<directory suffix=".php">./includes</directory>
|
|
71
|
+
</include>
|
|
72
|
+
<exclude>
|
|
73
|
+
<directory>./vendor</directory>
|
|
74
|
+
<directory>./tests</directory>
|
|
75
|
+
</exclude>
|
|
76
|
+
</coverage>
|
|
77
|
+
|
|
78
|
+
<php>
|
|
79
|
+
<env name="WP_ENVIRONMENT_TYPE" value="test"/>
|
|
80
|
+
<env name="WP_DEBUG" value="true"/>
|
|
81
|
+
<env name="WP_DEBUG_LOG" value="false"/>
|
|
82
|
+
<env name="WP_DEBUG_DISPLAY" value="false"/>
|
|
83
|
+
<env name="SCRIPT_DEBUG" value="false"/>
|
|
84
|
+
<const name="WP_TESTS_PHPUNIT_POLYFILLS_PATH" value="vendor/yoast/phpunit-polyfills"/>
|
|
85
|
+
<const name="PLUGIN_PREFIX_TESTING" value="true"/>
|
|
86
|
+
</php>
|
|
87
|
+
</phpunit>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**IMPORTANT**: Replace `PLUGIN_PREFIX_TESTING` with your actual plugin constant prefix.
|
|
91
|
+
|
|
92
|
+
### Test Bootstrap
|
|
93
|
+
|
|
94
|
+
File: `tests/bootstrap.php`
|
|
95
|
+
|
|
96
|
+
```php
|
|
97
|
+
<?php
|
|
98
|
+
/**
|
|
99
|
+
* PHPUnit Bootstrap for SilverAssist Plugin.
|
|
100
|
+
*
|
|
101
|
+
* @package SilverAssist\PluginName
|
|
102
|
+
*/
|
|
103
|
+
|
|
104
|
+
// Composer autoloader for stubs and dependencies.
|
|
105
|
+
require_once dirname( __DIR__ ) . '/vendor/autoload.php';
|
|
106
|
+
|
|
107
|
+
// Define test constants.
|
|
108
|
+
if ( ! defined( 'PLUGIN_PREFIX_TESTING' ) ) {
|
|
109
|
+
define( 'PLUGIN_PREFIX_TESTING', true );
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
define( 'PLUGIN_PREFIX_TESTS_DIR', __DIR__ );
|
|
113
|
+
define( 'PLUGIN_PREFIX_PLUGIN_DIR', dirname( __DIR__ ) );
|
|
114
|
+
define( 'PLUGIN_PREFIX_PLUGIN_FILE', PLUGIN_PREFIX_PLUGIN_DIR . '/plugin-main-file.php' );
|
|
115
|
+
|
|
116
|
+
// Define plugin constants (normally defined in main plugin file).
|
|
117
|
+
if ( ! defined( 'PLUGIN_PREFIX_VERSION' ) ) {
|
|
118
|
+
define( 'PLUGIN_PREFIX_VERSION', '1.0.0' );
|
|
119
|
+
}
|
|
120
|
+
if ( ! defined( 'PLUGIN_PREFIX_FILE' ) ) {
|
|
121
|
+
define( 'PLUGIN_PREFIX_FILE', PLUGIN_PREFIX_PLUGIN_FILE );
|
|
122
|
+
}
|
|
123
|
+
if ( ! defined( 'PLUGIN_PREFIX_PATH' ) ) {
|
|
124
|
+
define( 'PLUGIN_PREFIX_PATH', PLUGIN_PREFIX_PLUGIN_DIR . '/' );
|
|
125
|
+
}
|
|
126
|
+
if ( ! defined( 'PLUGIN_PREFIX_BASENAME' ) ) {
|
|
127
|
+
define( 'PLUGIN_PREFIX_BASENAME', 'plugin-name/plugin-main-file.php' );
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// WordPress test environment check.
|
|
131
|
+
$_tests_dir = getenv( 'WP_TESTS_DIR' );
|
|
132
|
+
|
|
133
|
+
if ( ! $_tests_dir ) {
|
|
134
|
+
$_tests_dir = rtrim( sys_get_temp_dir(), '/\\' ) . '/wordpress-tests-lib';
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Check if WordPress test suite is available.
|
|
138
|
+
$wp_tests_available = false;
|
|
139
|
+
$_tests_includes_dir = null;
|
|
140
|
+
|
|
141
|
+
if ( file_exists( $_tests_dir . '/includes/functions.php' ) ) {
|
|
142
|
+
// Standard wordpress-tests-lib structure.
|
|
143
|
+
$wp_tests_available = true;
|
|
144
|
+
$_tests_includes_dir = $_tests_dir . '/includes';
|
|
145
|
+
} elseif ( file_exists( $_tests_dir . '/tests/phpunit/includes/functions.php' ) ) {
|
|
146
|
+
// wordpress-develop repository structure.
|
|
147
|
+
$wp_tests_available = true;
|
|
148
|
+
$_tests_includes_dir = $_tests_dir . '/tests/phpunit/includes';
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Manually load the plugin being tested.
|
|
153
|
+
*/
|
|
154
|
+
function _manually_load_plugin() {
|
|
155
|
+
// Load composer autoloader.
|
|
156
|
+
if ( file_exists( PLUGIN_PREFIX_PLUGIN_DIR . '/vendor/autoload.php' ) ) {
|
|
157
|
+
require_once PLUGIN_PREFIX_PLUGIN_DIR . '/vendor/autoload.php';
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Note: Do NOT load main plugin file here.
|
|
161
|
+
// It will be loaded by WordPress Test Suite after WordPress loads.
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if ( $wp_tests_available ) {
|
|
165
|
+
// Load WordPress Test Suite.
|
|
166
|
+
require_once $_tests_includes_dir . '/functions.php';
|
|
167
|
+
|
|
168
|
+
tests_add_filter( 'muplugins_loaded', '_manually_load_plugin' );
|
|
169
|
+
|
|
170
|
+
// Start WordPress Test Suite.
|
|
171
|
+
require_once $_tests_includes_dir . '/bootstrap.php';
|
|
172
|
+
} else {
|
|
173
|
+
// WordPress Test Suite not available - tests will use mocks.
|
|
174
|
+
echo "Warning: WordPress Test Suite not found. Tests will run with limited functionality.\n";
|
|
175
|
+
_manually_load_plugin();
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
**Key Points:**
|
|
180
|
+
- Constants are defined with `if ( ! defined() )` guards to allow phpunit.xml.dist to set them
|
|
181
|
+
- WordPress Test Suite is auto-detected from `$WP_TESTS_DIR` env var or `/tmp/wordpress-tests-lib`
|
|
182
|
+
- Plugin is loaded via `muplugins_loaded` filter (NOT directly in bootstrap)
|
|
183
|
+
- Do NOT load the main plugin file in `_manually_load_plugin()` — WordPress Test Suite handles this
|
|
184
|
+
|
|
185
|
+
### Base Test Class
|
|
186
|
+
|
|
187
|
+
File: `tests/Helpers/TestCase.php`
|
|
188
|
+
|
|
189
|
+
```php
|
|
190
|
+
<?php
|
|
191
|
+
/**
|
|
192
|
+
* Base Test Case for SilverAssist Plugin.
|
|
193
|
+
*
|
|
194
|
+
* @package SilverAssist\PluginName
|
|
195
|
+
* @subpackage Tests\Helpers
|
|
196
|
+
*/
|
|
197
|
+
|
|
198
|
+
namespace SilverAssist\PluginName\Tests\Helpers;
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Base test case using WordPress Test Suite.
|
|
202
|
+
*
|
|
203
|
+
* All tests extend this class to have access to WordPress functions,
|
|
204
|
+
* factory methods, and proper database transaction rollback.
|
|
205
|
+
*/
|
|
206
|
+
abstract class TestCase extends \WP_UnitTestCase {
|
|
207
|
+
// Base test functionality.
|
|
208
|
+
// Add shared setup/teardown and helper methods here.
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### Directory Structure
|
|
213
|
+
|
|
214
|
+
```
|
|
215
|
+
tests/
|
|
216
|
+
├── bootstrap.php # Test bootstrap
|
|
217
|
+
├── README.md # Testing documentation
|
|
218
|
+
├── Unit/ # Unit tests (mirrors includes/ structure)
|
|
219
|
+
│ ├── Core/
|
|
220
|
+
│ │ └── PluginTest.php
|
|
221
|
+
│ └── Service/
|
|
222
|
+
│ └── ServiceNameTest.php
|
|
223
|
+
├── Integration/ # Integration tests
|
|
224
|
+
│ └── Admin/
|
|
225
|
+
│ └── SettingsTest.php
|
|
226
|
+
└── Helpers/ # Test utilities
|
|
227
|
+
├── TestCase.php # Base test class
|
|
228
|
+
└── Helpers.php # Test helper functions
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## Test Naming Conventions
|
|
234
|
+
|
|
235
|
+
### Lifecycle Methods (WordPress snake_case)
|
|
236
|
+
|
|
237
|
+
Since TestCase extends `WP_UnitTestCase`, use **WordPress-style snake_case** for lifecycle methods:
|
|
238
|
+
|
|
239
|
+
```php
|
|
240
|
+
// ✅ CORRECT: WordPress snake_case style (WP_UnitTestCase).
|
|
241
|
+
public function set_up(): void {
|
|
242
|
+
parent::set_up();
|
|
243
|
+
// Setup code.
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
public function tear_down(): void {
|
|
247
|
+
// Cleanup code.
|
|
248
|
+
parent::tear_down();
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
public static function set_up_before_class(): void {
|
|
252
|
+
parent::set_up_before_class();
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
public static function tear_down_after_class(): void {
|
|
256
|
+
parent::tear_down_after_class();
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// ❌ WRONG: PHPUnit camelCase style (don't use with WP_UnitTestCase).
|
|
260
|
+
public function setUp(): void // WRONG
|
|
261
|
+
public function tearDown(): void // WRONG
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### Test Methods (camelCase)
|
|
265
|
+
|
|
266
|
+
```php
|
|
267
|
+
// ✅ Test methods use camelCase.
|
|
268
|
+
public function testMethodReturnsExpectedValue(): void {
|
|
269
|
+
// Test implementation.
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
public function testActivatorCreatesDefaultOptions(): void {
|
|
273
|
+
// Test implementation.
|
|
274
|
+
}
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### Test File Naming
|
|
278
|
+
|
|
279
|
+
- Files: `ClassNameTest.php` (matches class being tested + `Test` suffix)
|
|
280
|
+
- Location: mirrors `includes/` structure under `tests/Unit/` or `tests/Integration/`
|
|
281
|
+
|
|
282
|
+
Example:
|
|
283
|
+
- `includes/Core/Plugin.php` → `tests/Unit/Core/PluginTest.php`
|
|
284
|
+
- `includes/Service/FormHandler.php` → `tests/Unit/Service/FormHandlerTest.php`
|
|
285
|
+
|
|
286
|
+
---
|
|
287
|
+
|
|
288
|
+
## WordPress Factory Pattern
|
|
289
|
+
|
|
290
|
+
**CRITICAL**: Use `static::factory()` — NOT `$this->factory` (deprecated since WordPress 6.1+).
|
|
291
|
+
|
|
292
|
+
### Creating Test Data
|
|
293
|
+
|
|
294
|
+
```php
|
|
295
|
+
public function set_up(): void {
|
|
296
|
+
parent::set_up();
|
|
297
|
+
|
|
298
|
+
// Create admin user.
|
|
299
|
+
$this->admin_user_id = static::factory()->user->create( [
|
|
300
|
+
'role' => 'administrator',
|
|
301
|
+
] );
|
|
302
|
+
\wp_set_current_user( $this->admin_user_id );
|
|
303
|
+
|
|
304
|
+
// Create test post.
|
|
305
|
+
$this->post_id = static::factory()->post->create( [
|
|
306
|
+
'post_title' => 'Test Post',
|
|
307
|
+
'post_status' => 'publish',
|
|
308
|
+
'post_type' => 'post',
|
|
309
|
+
] );
|
|
310
|
+
}
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
### Available Factories
|
|
314
|
+
|
|
315
|
+
```php
|
|
316
|
+
// Posts.
|
|
317
|
+
static::factory()->post->create( [ 'post_title' => 'Test', 'post_status' => 'publish' ] );
|
|
318
|
+
|
|
319
|
+
// Users.
|
|
320
|
+
static::factory()->user->create( [ 'role' => 'administrator' ] );
|
|
321
|
+
static::factory()->user->create( [ 'role' => 'editor' ] );
|
|
322
|
+
static::factory()->user->create( [ 'role' => 'subscriber' ] );
|
|
323
|
+
|
|
324
|
+
// Comments.
|
|
325
|
+
static::factory()->comment->create( [ 'comment_post_ID' => $post_id ] );
|
|
326
|
+
|
|
327
|
+
// Terms.
|
|
328
|
+
static::factory()->term->create( [ 'taxonomy' => 'category', 'name' => 'Test Category' ] );
|
|
329
|
+
|
|
330
|
+
// Categories.
|
|
331
|
+
static::factory()->category->create( [ 'name' => 'Test Category' ] );
|
|
332
|
+
|
|
333
|
+
// Create multiple at once.
|
|
334
|
+
$post_ids = static::factory()->post->create_many( 5, [ 'post_status' => 'publish' ] );
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### Create and Get
|
|
338
|
+
|
|
339
|
+
```php
|
|
340
|
+
// create() returns ID only.
|
|
341
|
+
$post_id = static::factory()->post->create( [ ... ] );
|
|
342
|
+
|
|
343
|
+
// create_and_get() returns full object.
|
|
344
|
+
$post = static::factory()->post->create_and_get( [ ... ] );
|
|
345
|
+
echo $post->post_title;
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
---
|
|
349
|
+
|
|
350
|
+
## Real WordPress Functions in Tests
|
|
351
|
+
|
|
352
|
+
With `WP_UnitTestCase`, you have access to ALL WordPress functions:
|
|
353
|
+
|
|
354
|
+
```php
|
|
355
|
+
// Options API.
|
|
356
|
+
\update_option( 'key', 'value' );
|
|
357
|
+
$value = \get_option( 'key' );
|
|
358
|
+
\delete_option( 'key' );
|
|
359
|
+
|
|
360
|
+
// User functions.
|
|
361
|
+
\wp_set_current_user( $user_id );
|
|
362
|
+
$can_edit = \current_user_can( 'edit_posts' );
|
|
363
|
+
|
|
364
|
+
// Post functions.
|
|
365
|
+
\wp_delete_post( $post_id, true );
|
|
366
|
+
$post = \get_post( $post_id );
|
|
367
|
+
|
|
368
|
+
// Hooks.
|
|
369
|
+
\has_action( 'hook_name', $callback );
|
|
370
|
+
\has_filter( 'filter_name', $callback );
|
|
371
|
+
\add_action( 'hook_name', $callback );
|
|
372
|
+
\do_action( 'hook_name', $args );
|
|
373
|
+
|
|
374
|
+
// Transients.
|
|
375
|
+
\set_transient( 'key', 'value', 3600 );
|
|
376
|
+
$value = \get_transient( 'key' );
|
|
377
|
+
\delete_transient( 'key' );
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
---
|
|
381
|
+
|
|
382
|
+
## Database Schema Changes in Tests
|
|
383
|
+
|
|
384
|
+
### The Problem
|
|
385
|
+
|
|
386
|
+
**CRITICAL**: `CREATE TABLE` triggers an implicit MySQL `COMMIT` which breaks WordPress Test Suite's transaction-based rollback.
|
|
387
|
+
|
|
388
|
+
- WordPress Test Suite wraps each test in `START TRANSACTION`
|
|
389
|
+
- After each test, it `ROLLBACK`s to restore clean state
|
|
390
|
+
- `CREATE TABLE` triggers implicit COMMIT, breaking rollback
|
|
391
|
+
- Result: Tables persist incorrectly or disappear between tests
|
|
392
|
+
|
|
393
|
+
### The Solution: `wpSetUpBeforeClass()`
|
|
394
|
+
|
|
395
|
+
Use `wpSetUpBeforeClass()` for DDL statements — it runs ONCE before all tests in the class, outside the transaction system:
|
|
396
|
+
|
|
397
|
+
```php
|
|
398
|
+
class YourTest extends TestCase {
|
|
399
|
+
/**
|
|
400
|
+
* Create shared fixtures before class.
|
|
401
|
+
*
|
|
402
|
+
* Runs ONCE before any tests in the class.
|
|
403
|
+
* Use this for CREATE TABLE statements.
|
|
404
|
+
*
|
|
405
|
+
* @param WP_UnitTest_Factory $factory Factory instance.
|
|
406
|
+
*/
|
|
407
|
+
public static function wpSetUpBeforeClass( $factory ): void {
|
|
408
|
+
global $wpdb;
|
|
409
|
+
|
|
410
|
+
// ✅ CREATE TABLE happens outside transaction system.
|
|
411
|
+
// Safe to use here - runs once before all tests.
|
|
412
|
+
Activator::create_tables();
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Setup test environment.
|
|
417
|
+
*
|
|
418
|
+
* Runs BEFORE EACH test. DO NOT create tables here.
|
|
419
|
+
*/
|
|
420
|
+
public function set_up(): void {
|
|
421
|
+
parent::set_up();
|
|
422
|
+
|
|
423
|
+
// ✅ TRUNCATE is safe - doesn't trigger COMMIT.
|
|
424
|
+
$this->clean_table_data();
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Clean table data.
|
|
429
|
+
*
|
|
430
|
+
* @return void
|
|
431
|
+
*/
|
|
432
|
+
protected function clean_table_data(): void {
|
|
433
|
+
global $wpdb;
|
|
434
|
+
$table_name = $wpdb->prefix . 'your_table';
|
|
435
|
+
|
|
436
|
+
// TRUNCATE doesn't trigger COMMIT - safe for test isolation.
|
|
437
|
+
// phpcs:ignore WordPress.DB.DirectDatabaseQuery
|
|
438
|
+
$wpdb->query( $wpdb->prepare( 'TRUNCATE TABLE %i', $table_name ) );
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
### MySQL Statements Reference
|
|
444
|
+
|
|
445
|
+
**Trigger Implicit COMMIT (use only in `wpSetUpBeforeClass`):**
|
|
446
|
+
- `CREATE TABLE` / `DROP TABLE`
|
|
447
|
+
- `CREATE DATABASE` / `DROP DATABASE`
|
|
448
|
+
- `ALTER TABLE`
|
|
449
|
+
- `RENAME TABLE`
|
|
450
|
+
|
|
451
|
+
**Safe for `set_up()` / `tear_down()`:**
|
|
452
|
+
- `TRUNCATE TABLE` (safe in WordPress Test Suite context)
|
|
453
|
+
- `INSERT` / `UPDATE` / `DELETE` (regular DML)
|
|
454
|
+
- `SELECT` queries
|
|
455
|
+
|
|
456
|
+
---
|
|
457
|
+
|
|
458
|
+
## Common Test Patterns
|
|
459
|
+
|
|
460
|
+
### Testing a Singleton Component
|
|
461
|
+
|
|
462
|
+
```php
|
|
463
|
+
<?php
|
|
464
|
+
namespace SilverAssist\PluginName\Tests\Unit\Core;
|
|
465
|
+
|
|
466
|
+
use SilverAssist\PluginName\Tests\Helpers\TestCase;
|
|
467
|
+
use SilverAssist\PluginName\Core\Plugin;
|
|
468
|
+
|
|
469
|
+
class PluginTest extends TestCase {
|
|
470
|
+
/**
|
|
471
|
+
* Test singleton returns same instance.
|
|
472
|
+
*/
|
|
473
|
+
public function testInstanceReturnsSameObject(): void {
|
|
474
|
+
$instance1 = Plugin::instance();
|
|
475
|
+
$instance2 = Plugin::instance();
|
|
476
|
+
|
|
477
|
+
$this->assertSame( $instance1, $instance2 );
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Test plugin implements LoadableInterface.
|
|
482
|
+
*/
|
|
483
|
+
public function testImplementsLoadableInterface(): void {
|
|
484
|
+
$plugin = Plugin::instance();
|
|
485
|
+
|
|
486
|
+
$this->assertInstanceOf(
|
|
487
|
+
\SilverAssist\PluginName\Core\Interfaces\LoadableInterface::class,
|
|
488
|
+
$plugin
|
|
489
|
+
);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* Test priority value.
|
|
494
|
+
*/
|
|
495
|
+
public function testGetPriorityReturnsTen(): void {
|
|
496
|
+
$plugin = Plugin::instance();
|
|
497
|
+
|
|
498
|
+
$this->assertSame( 10, $plugin->get_priority() );
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
### Testing Options/Settings
|
|
504
|
+
|
|
505
|
+
```php
|
|
506
|
+
public function testActivatorSetsDefaultOptions(): void {
|
|
507
|
+
// Ensure option doesn't exist.
|
|
508
|
+
\delete_option( 'plugin_prefix_settings' );
|
|
509
|
+
|
|
510
|
+
// Run activation.
|
|
511
|
+
Activator::activate();
|
|
512
|
+
|
|
513
|
+
// Verify defaults were set.
|
|
514
|
+
$settings = \get_option( 'plugin_prefix_settings' );
|
|
515
|
+
$this->assertIsArray( $settings );
|
|
516
|
+
$this->assertTrue( $settings['enabled'] );
|
|
517
|
+
}
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
### Testing Hooks Registration
|
|
521
|
+
|
|
522
|
+
```php
|
|
523
|
+
public function testInitRegistersHooks(): void {
|
|
524
|
+
$instance = MyComponent::instance();
|
|
525
|
+
$instance->init();
|
|
526
|
+
|
|
527
|
+
$this->assertNotFalse(
|
|
528
|
+
\has_action( 'admin_menu', [ $instance, 'register_menu' ] )
|
|
529
|
+
);
|
|
530
|
+
$this->assertNotFalse(
|
|
531
|
+
\has_filter( 'plugin_action_links', [ $instance, 'add_action_links' ] )
|
|
532
|
+
);
|
|
533
|
+
}
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
### Testing Capability Checks
|
|
537
|
+
|
|
538
|
+
```php
|
|
539
|
+
public function testNonAdminCannotAccessSettings(): void {
|
|
540
|
+
// Create subscriber user.
|
|
541
|
+
$subscriber_id = static::factory()->user->create( [ 'role' => 'subscriber' ] );
|
|
542
|
+
\wp_set_current_user( $subscriber_id );
|
|
543
|
+
|
|
544
|
+
// Should not pass capability check.
|
|
545
|
+
$this->assertFalse( \current_user_can( 'manage_options' ) );
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
public function testAdminCanAccessSettings(): void {
|
|
549
|
+
// Create admin user.
|
|
550
|
+
$admin_id = static::factory()->user->create( [ 'role' => 'administrator' ] );
|
|
551
|
+
\wp_set_current_user( $admin_id );
|
|
552
|
+
|
|
553
|
+
$this->assertTrue( \current_user_can( 'manage_options' ) );
|
|
554
|
+
}
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
### Testing Database Operations
|
|
558
|
+
|
|
559
|
+
```php
|
|
560
|
+
public static function wpSetUpBeforeClass( $factory ): void {
|
|
561
|
+
Activator::create_tables();
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
public function set_up(): void {
|
|
565
|
+
parent::set_up();
|
|
566
|
+
$this->clean_table_data();
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
public function testInsertAndRetrieveRecord(): void {
|
|
570
|
+
global $wpdb;
|
|
571
|
+
$table = $wpdb->prefix . 'plugin_table';
|
|
572
|
+
|
|
573
|
+
// Insert.
|
|
574
|
+
// phpcs:ignore WordPress.DB.DirectDatabaseQuery
|
|
575
|
+
$wpdb->insert(
|
|
576
|
+
$table,
|
|
577
|
+
[ 'created_at' => current_time( 'mysql' ) ],
|
|
578
|
+
[ '%s' ]
|
|
579
|
+
);
|
|
580
|
+
|
|
581
|
+
$id = $wpdb->insert_id;
|
|
582
|
+
$this->assertGreaterThan( 0, $id );
|
|
583
|
+
|
|
584
|
+
// Retrieve.
|
|
585
|
+
// phpcs:ignore WordPress.DB.DirectDatabaseQuery
|
|
586
|
+
$result = $wpdb->get_row(
|
|
587
|
+
$wpdb->prepare( 'SELECT * FROM %i WHERE id = %d', $table, $id )
|
|
588
|
+
);
|
|
589
|
+
|
|
590
|
+
$this->assertNotNull( $result );
|
|
591
|
+
$this->assertEquals( $id, $result->id );
|
|
592
|
+
}
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
---
|
|
596
|
+
|
|
597
|
+
## Running Tests
|
|
598
|
+
|
|
599
|
+
### Local Development
|
|
600
|
+
|
|
601
|
+
```bash
|
|
602
|
+
# Run all tests.
|
|
603
|
+
vendor/bin/phpunit
|
|
604
|
+
|
|
605
|
+
# Run specific test suite.
|
|
606
|
+
vendor/bin/phpunit --testsuite unit
|
|
607
|
+
vendor/bin/phpunit --testsuite integration
|
|
608
|
+
|
|
609
|
+
# Run specific test file.
|
|
610
|
+
vendor/bin/phpunit tests/Unit/Core/PluginTest.php
|
|
611
|
+
|
|
612
|
+
# Run specific test method.
|
|
613
|
+
vendor/bin/phpunit --filter testMethodName
|
|
614
|
+
|
|
615
|
+
# With coverage (requires xdebug).
|
|
616
|
+
vendor/bin/phpunit --coverage-html coverage/
|
|
617
|
+
vendor/bin/phpunit --coverage-text
|
|
618
|
+
|
|
619
|
+
# Human-readable output.
|
|
620
|
+
vendor/bin/phpunit --testdox
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
### WordPress Test Suite Installation
|
|
624
|
+
|
|
625
|
+
```bash
|
|
626
|
+
bash scripts/install-wp-tests.sh <db-name> <db-user> <db-pass> <db-host> <wp-version> <skip-database-creation>
|
|
627
|
+
|
|
628
|
+
# Example:
|
|
629
|
+
bash scripts/install-wp-tests.sh wordpress_test root 'root' localhost latest true
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
### CI Pipeline
|
|
633
|
+
|
|
634
|
+
Tests run automatically in CI via `quality-checks.yml`:
|
|
635
|
+
- PHP 8.2 (with coverage)
|
|
636
|
+
- PHP 8.3
|
|
637
|
+
- PHP 8.4
|
|
638
|
+
- WordPress compatibility matrix (6.5, 6.6, 6.7, latest)
|
|
639
|
+
|
|
640
|
+
---
|
|
641
|
+
|
|
642
|
+
## Troubleshooting
|
|
643
|
+
|
|
644
|
+
### "Class WP_UnitTestCase not found"
|
|
645
|
+
|
|
646
|
+
WordPress Test Suite is not installed. Install it or set `$WP_TESTS_DIR`:
|
|
647
|
+
|
|
648
|
+
```bash
|
|
649
|
+
export WP_TESTS_DIR=/tmp/wordpress-tests-lib
|
|
650
|
+
bash scripts/install-wp-tests.sh wordpress_test root 'root' localhost latest true
|
|
651
|
+
```
|
|
652
|
+
|
|
653
|
+
### Tests pass locally but fail in CI
|
|
654
|
+
|
|
655
|
+
Common causes:
|
|
656
|
+
1. **Missing `set_up()` parent call**: Always call `parent::set_up()` first
|
|
657
|
+
2. **State leaking between tests**: Use `tear_down()` to clean up
|
|
658
|
+
3. **Database table issues**: Use `wpSetUpBeforeClass()` for DDL statements
|
|
659
|
+
4. **Timezone differences**: Use `current_time('mysql')` instead of `date()`
|
|
660
|
+
|
|
661
|
+
### "Table already exists" errors
|
|
662
|
+
|
|
663
|
+
Don't create tables in `set_up()`. Use `wpSetUpBeforeClass()` which runs once:
|
|
664
|
+
|
|
665
|
+
```php
|
|
666
|
+
public static function wpSetUpBeforeClass( $factory ): void {
|
|
667
|
+
Activator::create_tables(); // Uses dbDelta which handles "IF NOT EXISTS" internally.
|
|
668
|
+
}
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
### "Cannot modify header information"
|
|
672
|
+
|
|
673
|
+
Test is producing output before headers. Check for:
|
|
674
|
+
- `echo` statements in tested code
|
|
675
|
+
- Missing output buffering
|
|
676
|
+
- `beStrictAboutOutputDuringTests="true"` in phpunit.xml.dist (expected behavior)
|
|
677
|
+
|
|
678
|
+
### Slow tests
|
|
679
|
+
|
|
680
|
+
- Run only unit tests: `vendor/bin/phpunit --testsuite unit`
|
|
681
|
+
- Use `--filter` for specific tests
|
|
682
|
+
- Skip WordPress setup for non-integration tests: use `--skip-wp-setup` flag
|