@mytechtoday/augment-extensions 0.1.1 → 0.2.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/augment-extensions/domain-rules/wordpress/README.md +163 -0
- package/augment-extensions/domain-rules/wordpress/module.json +32 -0
- package/augment-extensions/domain-rules/wordpress/rules/coding-standards.md +617 -0
- package/augment-extensions/domain-rules/wordpress/rules/directory-structure.md +270 -0
- package/augment-extensions/domain-rules/wordpress/rules/file-patterns.md +423 -0
- package/augment-extensions/domain-rules/wordpress/rules/gutenberg-blocks.md +493 -0
- package/augment-extensions/domain-rules/wordpress/rules/performance.md +568 -0
- package/augment-extensions/domain-rules/wordpress/rules/plugin-development.md +510 -0
- package/augment-extensions/domain-rules/wordpress/rules/project-detection.md +251 -0
- package/augment-extensions/domain-rules/wordpress/rules/rest-api.md +501 -0
- package/augment-extensions/domain-rules/wordpress/rules/security.md +564 -0
- package/augment-extensions/domain-rules/wordpress/rules/theme-development.md +388 -0
- package/augment-extensions/domain-rules/wordpress/rules/woocommerce.md +441 -0
- package/augment-extensions/domain-rules/wordpress-plugin/README.md +139 -0
- package/augment-extensions/domain-rules/wordpress-plugin/examples/ajax-plugin.md +1599 -0
- package/augment-extensions/domain-rules/wordpress-plugin/examples/custom-post-type-plugin.md +1727 -0
- package/augment-extensions/domain-rules/wordpress-plugin/examples/gutenberg-block-plugin.md +428 -0
- package/augment-extensions/domain-rules/wordpress-plugin/examples/gutenberg-block.md +422 -0
- package/augment-extensions/domain-rules/wordpress-plugin/examples/mvc-plugin.md +1623 -0
- package/augment-extensions/domain-rules/wordpress-plugin/examples/object-oriented-plugin.md +1343 -0
- package/augment-extensions/domain-rules/wordpress-plugin/examples/rest-endpoint.md +734 -0
- package/augment-extensions/domain-rules/wordpress-plugin/examples/settings-page-plugin.md +1350 -0
- package/augment-extensions/domain-rules/wordpress-plugin/examples/simple-procedural-plugin.md +503 -0
- package/augment-extensions/domain-rules/wordpress-plugin/examples/singleton-plugin.md +971 -0
- package/augment-extensions/domain-rules/wordpress-plugin/module.json +53 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/activation-hooks.md +770 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/admin-interface.md +874 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/ajax-handlers.md +629 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/asset-management.md +559 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/context-providers.md +709 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/cron-jobs.md +736 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/database-management.md +1057 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/documentation-standards.md +463 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/frontend-functionality.md +478 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/gutenberg-blocks.md +818 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/internationalization.md +416 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/migration.md +667 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/performance-optimization.md +878 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/plugin-architecture.md +693 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/plugin-structure.md +352 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/rest-api.md +818 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/scaffolding-workflow.md +624 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/security-best-practices.md +866 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/testing-patterns.md +1165 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/testing.md +414 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/vscode-integration.md +751 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/woocommerce-integration.md +949 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/wordpress-org-submission.md +458 -0
- package/augment-extensions/examples/gutenberg-block-plugin/README.md +101 -0
- package/augment-extensions/examples/gutenberg-block-plugin/examples/testimonial-block.md +428 -0
- package/augment-extensions/examples/gutenberg-block-plugin/module.json +40 -0
- package/augment-extensions/examples/rest-api-plugin/README.md +98 -0
- package/augment-extensions/examples/rest-api-plugin/examples/task-manager-api.md +1299 -0
- package/augment-extensions/examples/rest-api-plugin/module.json +40 -0
- package/augment-extensions/examples/woocommerce-extension/README.md +98 -0
- package/augment-extensions/examples/woocommerce-extension/examples/product-customizer.md +763 -0
- package/augment-extensions/examples/woocommerce-extension/module.json +40 -0
- package/augment-extensions/workflows/wordpress-plugin/README.md +232 -0
- package/augment-extensions/workflows/wordpress-plugin/ai-prompts.md +839 -0
- package/augment-extensions/workflows/wordpress-plugin/bead-decomposition-patterns.md +854 -0
- package/augment-extensions/workflows/wordpress-plugin/examples/complete-plugin-example.md +540 -0
- package/augment-extensions/workflows/wordpress-plugin/examples/custom-post-type-example.md +1083 -0
- package/augment-extensions/workflows/wordpress-plugin/examples/feature-addition-workflow.md +669 -0
- package/augment-extensions/workflows/wordpress-plugin/examples/plugin-creation-workflow.md +597 -0
- package/augment-extensions/workflows/wordpress-plugin/examples/secure-form-handler-example.md +925 -0
- package/augment-extensions/workflows/wordpress-plugin/examples/security-audit-workflow.md +752 -0
- package/augment-extensions/workflows/wordpress-plugin/examples/wordpress-org-submission-workflow.md +773 -0
- package/augment-extensions/workflows/wordpress-plugin/module.json +49 -0
- package/augment-extensions/workflows/wordpress-plugin/rules/best-practices.md +942 -0
- package/augment-extensions/workflows/wordpress-plugin/rules/development-workflow.md +702 -0
- package/augment-extensions/workflows/wordpress-plugin/rules/submission-workflow.md +728 -0
- package/augment-extensions/workflows/wordpress-plugin/rules/testing-workflow.md +775 -0
- package/cli/dist/cli.js +5 -1
- package/cli/dist/cli.js.map +1 -1
- package/cli/dist/commands/show.d.ts.map +1 -1
- package/cli/dist/commands/show.js +41 -0
- package/cli/dist/commands/show.js.map +1 -1
- package/modules.md +52 -0
- package/package.json +1 -1
|
@@ -0,0 +1,1165 @@
|
|
|
1
|
+
# Testing Patterns for WordPress Plugins
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
This document provides comprehensive testing patterns and best practices for WordPress plugin development, including PHPUnit setup, unit tests, integration tests, and WordPress-specific testing strategies.
|
|
6
|
+
|
|
7
|
+
## Testing Environment Setup
|
|
8
|
+
|
|
9
|
+
### Install WordPress Test Suite
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
#!/bin/bash
|
|
13
|
+
# bin/install-wp-tests.sh
|
|
14
|
+
|
|
15
|
+
DB_NAME=${1-wordpress_test}
|
|
16
|
+
DB_USER=${2-root}
|
|
17
|
+
DB_PASS=${3-}
|
|
18
|
+
DB_HOST=${4-localhost}
|
|
19
|
+
WP_VERSION=${5-latest}
|
|
20
|
+
|
|
21
|
+
# Download WordPress test suite
|
|
22
|
+
svn co --quiet https://develop.svn.wordpress.org/tags/$WP_VERSION/tests/phpunit/includes/ /tmp/wordpress-tests-lib/includes/
|
|
23
|
+
svn co --quiet https://develop.svn.wordpress.org/tags/$WP_VERSION/tests/phpunit/data/ /tmp/wordpress-tests-lib/data/
|
|
24
|
+
|
|
25
|
+
# Create test database
|
|
26
|
+
mysql --user="$DB_USER" --password="$DB_PASS" --execute="CREATE DATABASE IF NOT EXISTS $DB_NAME;"
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### PHPUnit Configuration
|
|
30
|
+
|
|
31
|
+
**phpunit.xml:**
|
|
32
|
+
```xml
|
|
33
|
+
<?xml version="1.0"?>
|
|
34
|
+
<phpunit
|
|
35
|
+
bootstrap="tests/bootstrap.php"
|
|
36
|
+
backupGlobals="false"
|
|
37
|
+
colors="true"
|
|
38
|
+
convertErrorsToExceptions="true"
|
|
39
|
+
convertNoticesToExceptions="true"
|
|
40
|
+
convertWarningsToExceptions="true"
|
|
41
|
+
>
|
|
42
|
+
<testsuites>
|
|
43
|
+
<testsuite name="unit">
|
|
44
|
+
<directory prefix="test-" suffix=".php">./tests/unit/</directory>
|
|
45
|
+
</testsuite>
|
|
46
|
+
<testsuite name="integration">
|
|
47
|
+
<directory prefix="test-" suffix=".php">./tests/integration/</directory>
|
|
48
|
+
</testsuite>
|
|
49
|
+
</testsuites>
|
|
50
|
+
<filter>
|
|
51
|
+
<whitelist processUncoveredFilesFromWhitelist="true">
|
|
52
|
+
<directory suffix=".php">./includes/</directory>
|
|
53
|
+
<directory suffix=".php">./admin/</directory>
|
|
54
|
+
<directory suffix=".php">./public/</directory>
|
|
55
|
+
</whitelist>
|
|
56
|
+
</filter>
|
|
57
|
+
</phpunit>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Bootstrap File
|
|
61
|
+
|
|
62
|
+
**tests/bootstrap.php:**
|
|
63
|
+
```php
|
|
64
|
+
<?php
|
|
65
|
+
/**
|
|
66
|
+
* PHPUnit bootstrap file
|
|
67
|
+
*/
|
|
68
|
+
|
|
69
|
+
// Composer autoloader
|
|
70
|
+
require_once dirname( dirname( __FILE__ ) ) . '/vendor/autoload.php';
|
|
71
|
+
|
|
72
|
+
// WordPress tests directory
|
|
73
|
+
$_tests_dir = getenv( 'WP_TESTS_DIR' );
|
|
74
|
+
|
|
75
|
+
if ( ! $_tests_dir ) {
|
|
76
|
+
$_tests_dir = rtrim( sys_get_temp_dir(), '/\\' ) . '/wordpress-tests-lib';
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if ( ! file_exists( $_tests_dir . '/includes/functions.php' ) ) {
|
|
80
|
+
echo "Could not find $_tests_dir/includes/functions.php\n";
|
|
81
|
+
exit( 1 );
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Give access to tests_add_filter() function
|
|
85
|
+
require_once $_tests_dir . '/includes/functions.php';
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Manually load the plugin being tested
|
|
89
|
+
*/
|
|
90
|
+
function _manually_load_plugin() {
|
|
91
|
+
require dirname( dirname( __FILE__ ) ) . '/my-plugin.php';
|
|
92
|
+
}
|
|
93
|
+
tests_add_filter( 'muplugins_loaded', '_manually_load_plugin' );
|
|
94
|
+
|
|
95
|
+
// Start up the WP testing environment
|
|
96
|
+
require $_tests_dir . '/includes/bootstrap.php';
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Unit Testing Patterns
|
|
102
|
+
|
|
103
|
+
### Test Class Structure
|
|
104
|
+
|
|
105
|
+
```php
|
|
106
|
+
<?php
|
|
107
|
+
/**
|
|
108
|
+
* Unit test for custom class
|
|
109
|
+
*/
|
|
110
|
+
class Test_My_Plugin_Class extends WP_UnitTestCase {
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Set up before each test
|
|
114
|
+
*/
|
|
115
|
+
public function setUp(): void {
|
|
116
|
+
parent::setUp();
|
|
117
|
+
|
|
118
|
+
// Set up test data
|
|
119
|
+
$this->instance = new My_Plugin_Class();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Tear down after each test
|
|
124
|
+
*/
|
|
125
|
+
public function tearDown(): void {
|
|
126
|
+
parent::tearDown();
|
|
127
|
+
|
|
128
|
+
// Clean up test data
|
|
129
|
+
unset( $this->instance );
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Test method
|
|
134
|
+
*/
|
|
135
|
+
public function test_method_name() {
|
|
136
|
+
$result = $this->instance->method_name( 'test' );
|
|
137
|
+
|
|
138
|
+
$this->assertEquals( 'expected', $result );
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Testing WordPress Functions
|
|
144
|
+
|
|
145
|
+
```php
|
|
146
|
+
<?php
|
|
147
|
+
/**
|
|
148
|
+
* Test WordPress function integration
|
|
149
|
+
*/
|
|
150
|
+
class Test_Plugin_Functions extends WP_UnitTestCase {
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Test option storage
|
|
154
|
+
*/
|
|
155
|
+
public function test_save_plugin_option() {
|
|
156
|
+
$option_name = 'my_plugin_option';
|
|
157
|
+
$option_value = 'test value';
|
|
158
|
+
|
|
159
|
+
update_option( $option_name, $option_value );
|
|
160
|
+
|
|
161
|
+
$saved_value = get_option( $option_name );
|
|
162
|
+
|
|
163
|
+
$this->assertEquals( $option_value, $saved_value );
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Test post meta
|
|
168
|
+
*/
|
|
169
|
+
public function test_save_post_meta() {
|
|
170
|
+
$post_id = $this->factory->post->create();
|
|
171
|
+
$meta_key = '_custom_meta';
|
|
172
|
+
$meta_value = 'test value';
|
|
173
|
+
|
|
174
|
+
update_post_meta( $post_id, $meta_key, $meta_value );
|
|
175
|
+
|
|
176
|
+
$saved_value = get_post_meta( $post_id, $meta_key, true );
|
|
177
|
+
|
|
178
|
+
$this->assertEquals( $meta_value, $saved_value );
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Testing Custom Post Types
|
|
184
|
+
|
|
185
|
+
```php
|
|
186
|
+
<?php
|
|
187
|
+
/**
|
|
188
|
+
* Test custom post type registration
|
|
189
|
+
*/
|
|
190
|
+
class Test_Custom_Post_Type extends WP_UnitTestCase {
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Test CPT is registered
|
|
194
|
+
*/
|
|
195
|
+
public function test_cpt_registered() {
|
|
196
|
+
$this->assertTrue( post_type_exists( 'my_custom_post_type' ) );
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Test CPT creation
|
|
201
|
+
*/
|
|
202
|
+
public function test_create_cpt() {
|
|
203
|
+
$post_id = $this->factory->post->create( array(
|
|
204
|
+
'post_type' => 'my_custom_post_type',
|
|
205
|
+
'post_title' => 'Test Post',
|
|
206
|
+
'post_status' => 'publish',
|
|
207
|
+
) );
|
|
208
|
+
|
|
209
|
+
$this->assertGreaterThan( 0, $post_id );
|
|
210
|
+
|
|
211
|
+
$post = get_post( $post_id );
|
|
212
|
+
$this->assertEquals( 'my_custom_post_type', $post->post_type );
|
|
213
|
+
$this->assertEquals( 'Test Post', $post->post_title );
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Test CPT labels
|
|
218
|
+
*/
|
|
219
|
+
public function test_cpt_labels() {
|
|
220
|
+
$post_type_object = get_post_type_object( 'my_custom_post_type' );
|
|
221
|
+
|
|
222
|
+
$this->assertEquals( 'My Custom Posts', $post_type_object->labels->name );
|
|
223
|
+
$this->assertEquals( 'My Custom Post', $post_type_object->labels->singular_name );
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### Testing Taxonomies
|
|
229
|
+
|
|
230
|
+
```php
|
|
231
|
+
<?php
|
|
232
|
+
/**
|
|
233
|
+
* Test custom taxonomy
|
|
234
|
+
*/
|
|
235
|
+
class Test_Custom_Taxonomy extends WP_UnitTestCase {
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Test taxonomy is registered
|
|
239
|
+
*/
|
|
240
|
+
public function test_taxonomy_registered() {
|
|
241
|
+
$this->assertTrue( taxonomy_exists( 'my_custom_taxonomy' ) );
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Test term creation
|
|
246
|
+
*/
|
|
247
|
+
public function test_create_term() {
|
|
248
|
+
$term = wp_insert_term( 'Test Term', 'my_custom_taxonomy' );
|
|
249
|
+
|
|
250
|
+
$this->assertIsArray( $term );
|
|
251
|
+
$this->assertArrayHasKey( 'term_id', $term );
|
|
252
|
+
|
|
253
|
+
$term_object = get_term( $term['term_id'], 'my_custom_taxonomy' );
|
|
254
|
+
$this->assertEquals( 'Test Term', $term_object->name );
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Test term assignment
|
|
259
|
+
*/
|
|
260
|
+
public function test_assign_term_to_post() {
|
|
261
|
+
$post_id = $this->factory->post->create( array(
|
|
262
|
+
'post_type' => 'my_custom_post_type',
|
|
263
|
+
) );
|
|
264
|
+
|
|
265
|
+
$term = wp_insert_term( 'Test Term', 'my_custom_taxonomy' );
|
|
266
|
+
|
|
267
|
+
wp_set_object_terms( $post_id, $term['term_id'], 'my_custom_taxonomy' );
|
|
268
|
+
|
|
269
|
+
$terms = wp_get_object_terms( $post_id, 'my_custom_taxonomy' );
|
|
270
|
+
|
|
271
|
+
$this->assertCount( 1, $terms );
|
|
272
|
+
$this->assertEquals( 'Test Term', $terms[0]->name );
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
---
|
|
278
|
+
|
|
279
|
+
## Integration Testing Patterns
|
|
280
|
+
|
|
281
|
+
### Testing Hooks and Filters
|
|
282
|
+
|
|
283
|
+
```php
|
|
284
|
+
<?php
|
|
285
|
+
/**
|
|
286
|
+
* Test hooks and filters
|
|
287
|
+
*/
|
|
288
|
+
class Test_Plugin_Hooks extends WP_UnitTestCase {
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Test action hook is registered
|
|
292
|
+
*/
|
|
293
|
+
public function test_action_hook_registered() {
|
|
294
|
+
$this->assertGreaterThan(
|
|
295
|
+
0,
|
|
296
|
+
has_action( 'init', 'my_plugin_init_function' )
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Test filter hook is registered
|
|
302
|
+
*/
|
|
303
|
+
public function test_filter_hook_registered() {
|
|
304
|
+
$this->assertGreaterThan(
|
|
305
|
+
0,
|
|
306
|
+
has_filter( 'the_content', 'my_plugin_filter_content' )
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Test filter modifies content
|
|
312
|
+
*/
|
|
313
|
+
public function test_filter_modifies_content() {
|
|
314
|
+
$original_content = 'Original content';
|
|
315
|
+
$filtered_content = apply_filters( 'the_content', $original_content );
|
|
316
|
+
|
|
317
|
+
$this->assertStringContainsString( 'Original content', $filtered_content );
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Test action executes
|
|
322
|
+
*/
|
|
323
|
+
public function test_action_executes() {
|
|
324
|
+
// Set up a flag to check if action executed
|
|
325
|
+
$executed = false;
|
|
326
|
+
|
|
327
|
+
add_action( 'my_plugin_custom_action', function() use ( &$executed ) {
|
|
328
|
+
$executed = true;
|
|
329
|
+
} );
|
|
330
|
+
|
|
331
|
+
do_action( 'my_plugin_custom_action' );
|
|
332
|
+
|
|
333
|
+
$this->assertTrue( $executed );
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
### Testing AJAX Handlers
|
|
339
|
+
|
|
340
|
+
```php
|
|
341
|
+
<?php
|
|
342
|
+
/**
|
|
343
|
+
* Test AJAX handlers
|
|
344
|
+
*/
|
|
345
|
+
class Test_Plugin_AJAX extends WP_Ajax_UnitTestCase {
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Test AJAX handler for logged-in users
|
|
349
|
+
*/
|
|
350
|
+
public function test_ajax_handler_logged_in() {
|
|
351
|
+
// Create and log in user
|
|
352
|
+
$user_id = $this->factory->user->create( array( 'role' => 'administrator' ) );
|
|
353
|
+
wp_set_current_user( $user_id );
|
|
354
|
+
|
|
355
|
+
// Set up POST data
|
|
356
|
+
$_POST['nonce'] = wp_create_nonce( 'my_plugin_ajax_nonce' );
|
|
357
|
+
$_POST['data'] = 'test data';
|
|
358
|
+
|
|
359
|
+
// Make AJAX request
|
|
360
|
+
try {
|
|
361
|
+
$this->_handleAjax( 'my_plugin_ajax_action' );
|
|
362
|
+
} catch ( WPAjaxDieContinueException $e ) {
|
|
363
|
+
// Expected exception
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Check response
|
|
367
|
+
$response = json_decode( $this->_last_response );
|
|
368
|
+
$this->assertTrue( $response->success );
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Test AJAX handler for non-logged-in users
|
|
373
|
+
*/
|
|
374
|
+
public function test_ajax_handler_not_logged_in() {
|
|
375
|
+
// Set up POST data
|
|
376
|
+
$_POST['data'] = 'test data';
|
|
377
|
+
|
|
378
|
+
// Make AJAX request
|
|
379
|
+
try {
|
|
380
|
+
$this->_handleAjax( 'my_plugin_ajax_action' );
|
|
381
|
+
} catch ( WPAjaxDieStopException $e ) {
|
|
382
|
+
// Expected exception for unauthorized access
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Check response indicates failure
|
|
386
|
+
$response = json_decode( $this->_last_response );
|
|
387
|
+
$this->assertFalse( $response->success );
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
### Testing REST API Endpoints
|
|
393
|
+
|
|
394
|
+
```php
|
|
395
|
+
<?php
|
|
396
|
+
/**
|
|
397
|
+
* Test REST API endpoints
|
|
398
|
+
*/
|
|
399
|
+
class Test_Plugin_REST_API extends WP_Test_REST_TestCase {
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Set up before tests
|
|
403
|
+
*/
|
|
404
|
+
public function setUp(): void {
|
|
405
|
+
parent::setUp();
|
|
406
|
+
|
|
407
|
+
// Create test user
|
|
408
|
+
$this->user_id = $this->factory->user->create( array(
|
|
409
|
+
'role' => 'administrator',
|
|
410
|
+
) );
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Test GET endpoint
|
|
415
|
+
*/
|
|
416
|
+
public function test_get_endpoint() {
|
|
417
|
+
wp_set_current_user( $this->user_id );
|
|
418
|
+
|
|
419
|
+
$request = new WP_REST_Request( 'GET', '/my-plugin/v1/items' );
|
|
420
|
+
$response = rest_do_request( $request );
|
|
421
|
+
|
|
422
|
+
$this->assertEquals( 200, $response->get_status() );
|
|
423
|
+
|
|
424
|
+
$data = $response->get_data();
|
|
425
|
+
$this->assertIsArray( $data );
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Test POST endpoint
|
|
430
|
+
*/
|
|
431
|
+
public function test_post_endpoint() {
|
|
432
|
+
wp_set_current_user( $this->user_id );
|
|
433
|
+
|
|
434
|
+
$request = new WP_REST_Request( 'POST', '/my-plugin/v1/items' );
|
|
435
|
+
$request->set_body_params( array(
|
|
436
|
+
'title' => 'Test Item',
|
|
437
|
+
'content' => 'Test content',
|
|
438
|
+
) );
|
|
439
|
+
|
|
440
|
+
$response = rest_do_request( $request );
|
|
441
|
+
|
|
442
|
+
$this->assertEquals( 201, $response->get_status() );
|
|
443
|
+
|
|
444
|
+
$data = $response->get_data();
|
|
445
|
+
$this->assertEquals( 'Test Item', $data['title'] );
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Test endpoint permissions
|
|
450
|
+
*/
|
|
451
|
+
public function test_endpoint_permissions() {
|
|
452
|
+
// Not logged in
|
|
453
|
+
$request = new WP_REST_Request( 'POST', '/my-plugin/v1/items' );
|
|
454
|
+
$response = rest_do_request( $request );
|
|
455
|
+
|
|
456
|
+
$this->assertEquals( 401, $response->get_status() );
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
---
|
|
462
|
+
|
|
463
|
+
## Database Testing Patterns
|
|
464
|
+
|
|
465
|
+
### Testing Custom Tables
|
|
466
|
+
|
|
467
|
+
```php
|
|
468
|
+
<?php
|
|
469
|
+
/**
|
|
470
|
+
* Test custom database table
|
|
471
|
+
*/
|
|
472
|
+
class Test_Custom_Table extends WP_UnitTestCase {
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Table name
|
|
476
|
+
*/
|
|
477
|
+
private $table_name;
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* Set up before tests
|
|
481
|
+
*/
|
|
482
|
+
public function setUp(): void {
|
|
483
|
+
parent::setUp();
|
|
484
|
+
|
|
485
|
+
global $wpdb;
|
|
486
|
+
$this->table_name = $wpdb->prefix . 'my_plugin_table';
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Test table exists
|
|
491
|
+
*/
|
|
492
|
+
public function test_table_exists() {
|
|
493
|
+
global $wpdb;
|
|
494
|
+
|
|
495
|
+
$table_exists = $wpdb->get_var(
|
|
496
|
+
$wpdb->prepare(
|
|
497
|
+
"SHOW TABLES LIKE %s",
|
|
498
|
+
$this->table_name
|
|
499
|
+
)
|
|
500
|
+
);
|
|
501
|
+
|
|
502
|
+
$this->assertEquals( $this->table_name, $table_exists );
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* Test insert data
|
|
507
|
+
*/
|
|
508
|
+
public function test_insert_data() {
|
|
509
|
+
global $wpdb;
|
|
510
|
+
|
|
511
|
+
$data = array(
|
|
512
|
+
'name' => 'Test Name',
|
|
513
|
+
'value' => 'Test Value',
|
|
514
|
+
);
|
|
515
|
+
|
|
516
|
+
$result = $wpdb->insert( $this->table_name, $data );
|
|
517
|
+
|
|
518
|
+
$this->assertEquals( 1, $result );
|
|
519
|
+
$this->assertGreaterThan( 0, $wpdb->insert_id );
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* Test query data
|
|
524
|
+
*/
|
|
525
|
+
public function test_query_data() {
|
|
526
|
+
global $wpdb;
|
|
527
|
+
|
|
528
|
+
// Insert test data
|
|
529
|
+
$wpdb->insert( $this->table_name, array(
|
|
530
|
+
'name' => 'Test Name',
|
|
531
|
+
'value' => 'Test Value',
|
|
532
|
+
) );
|
|
533
|
+
|
|
534
|
+
// Query data
|
|
535
|
+
$result = $wpdb->get_row(
|
|
536
|
+
$wpdb->prepare(
|
|
537
|
+
"SELECT * FROM {$this->table_name} WHERE name = %s",
|
|
538
|
+
'Test Name'
|
|
539
|
+
)
|
|
540
|
+
);
|
|
541
|
+
|
|
542
|
+
$this->assertEquals( 'Test Name', $result->name );
|
|
543
|
+
$this->assertEquals( 'Test Value', $result->value );
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
### Testing Transients
|
|
549
|
+
|
|
550
|
+
```php
|
|
551
|
+
<?php
|
|
552
|
+
/**
|
|
553
|
+
* Test transient caching
|
|
554
|
+
*/
|
|
555
|
+
class Test_Plugin_Transients extends WP_UnitTestCase {
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* Test set and get transient
|
|
559
|
+
*/
|
|
560
|
+
public function test_set_get_transient() {
|
|
561
|
+
$transient_name = 'my_plugin_transient';
|
|
562
|
+
$transient_value = array( 'key' => 'value' );
|
|
563
|
+
|
|
564
|
+
set_transient( $transient_name, $transient_value, HOUR_IN_SECONDS );
|
|
565
|
+
|
|
566
|
+
$retrieved_value = get_transient( $transient_name );
|
|
567
|
+
|
|
568
|
+
$this->assertEquals( $transient_value, $retrieved_value );
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
/**
|
|
572
|
+
* Test transient expiration
|
|
573
|
+
*/
|
|
574
|
+
public function test_transient_expiration() {
|
|
575
|
+
$transient_name = 'my_plugin_transient_expire';
|
|
576
|
+
$transient_value = 'test value';
|
|
577
|
+
|
|
578
|
+
set_transient( $transient_name, $transient_value, 1 );
|
|
579
|
+
|
|
580
|
+
sleep( 2 );
|
|
581
|
+
|
|
582
|
+
$retrieved_value = get_transient( $transient_name );
|
|
583
|
+
|
|
584
|
+
$this->assertFalse( $retrieved_value );
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
/**
|
|
588
|
+
* Test delete transient
|
|
589
|
+
*/
|
|
590
|
+
public function test_delete_transient() {
|
|
591
|
+
$transient_name = 'my_plugin_transient_delete';
|
|
592
|
+
$transient_value = 'test value';
|
|
593
|
+
|
|
594
|
+
set_transient( $transient_name, $transient_value, HOUR_IN_SECONDS );
|
|
595
|
+
|
|
596
|
+
delete_transient( $transient_name );
|
|
597
|
+
|
|
598
|
+
$retrieved_value = get_transient( $transient_name );
|
|
599
|
+
|
|
600
|
+
$this->assertFalse( $retrieved_value );
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
---
|
|
606
|
+
|
|
607
|
+
## WooCommerce Testing Patterns
|
|
608
|
+
|
|
609
|
+
### Testing WooCommerce Products
|
|
610
|
+
|
|
611
|
+
```php
|
|
612
|
+
<?php
|
|
613
|
+
/**
|
|
614
|
+
* Test WooCommerce product functionality
|
|
615
|
+
*/
|
|
616
|
+
class Test_WooCommerce_Product extends WP_UnitTestCase {
|
|
617
|
+
|
|
618
|
+
/**
|
|
619
|
+
* Test create simple product
|
|
620
|
+
*/
|
|
621
|
+
public function test_create_simple_product() {
|
|
622
|
+
$product = new WC_Product_Simple();
|
|
623
|
+
$product->set_name( 'Test Product' );
|
|
624
|
+
$product->set_regular_price( '10.00' );
|
|
625
|
+
$product->set_status( 'publish' );
|
|
626
|
+
$product->save();
|
|
627
|
+
|
|
628
|
+
$this->assertGreaterThan( 0, $product->get_id() );
|
|
629
|
+
$this->assertEquals( 'Test Product', $product->get_name() );
|
|
630
|
+
$this->assertEquals( '10.00', $product->get_regular_price() );
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
/**
|
|
634
|
+
* Test product meta
|
|
635
|
+
*/
|
|
636
|
+
public function test_product_meta() {
|
|
637
|
+
$product = new WC_Product_Simple();
|
|
638
|
+
$product->set_name( 'Test Product' );
|
|
639
|
+
$product->save();
|
|
640
|
+
|
|
641
|
+
$product->update_meta_data( '_custom_field', 'custom value' );
|
|
642
|
+
$product->save();
|
|
643
|
+
|
|
644
|
+
$custom_field = $product->get_meta( '_custom_field' );
|
|
645
|
+
|
|
646
|
+
$this->assertEquals( 'custom value', $custom_field );
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
```
|
|
650
|
+
|
|
651
|
+
### Testing WooCommerce Orders
|
|
652
|
+
|
|
653
|
+
```php
|
|
654
|
+
<?php
|
|
655
|
+
/**
|
|
656
|
+
* Test WooCommerce order functionality
|
|
657
|
+
*/
|
|
658
|
+
class Test_WooCommerce_Order extends WP_UnitTestCase {
|
|
659
|
+
|
|
660
|
+
/**
|
|
661
|
+
* Test create order
|
|
662
|
+
*/
|
|
663
|
+
public function test_create_order() {
|
|
664
|
+
$order = wc_create_order();
|
|
665
|
+
|
|
666
|
+
$this->assertInstanceOf( 'WC_Order', $order );
|
|
667
|
+
$this->assertGreaterThan( 0, $order->get_id() );
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
/**
|
|
671
|
+
* Test add product to order
|
|
672
|
+
*/
|
|
673
|
+
public function test_add_product_to_order() {
|
|
674
|
+
$product = new WC_Product_Simple();
|
|
675
|
+
$product->set_name( 'Test Product' );
|
|
676
|
+
$product->set_regular_price( '10.00' );
|
|
677
|
+
$product->save();
|
|
678
|
+
|
|
679
|
+
$order = wc_create_order();
|
|
680
|
+
$order->add_product( $product, 2 );
|
|
681
|
+
$order->calculate_totals();
|
|
682
|
+
|
|
683
|
+
$this->assertEquals( '20.00', $order->get_total() );
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
/**
|
|
687
|
+
* Test order status change
|
|
688
|
+
*/
|
|
689
|
+
public function test_order_status_change() {
|
|
690
|
+
$order = wc_create_order();
|
|
691
|
+
$order->set_status( 'processing' );
|
|
692
|
+
$order->save();
|
|
693
|
+
|
|
694
|
+
$this->assertEquals( 'processing', $order->get_status() );
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
---
|
|
700
|
+
|
|
701
|
+
## Mock and Stub Patterns
|
|
702
|
+
|
|
703
|
+
### Mocking External APIs
|
|
704
|
+
|
|
705
|
+
```php
|
|
706
|
+
<?php
|
|
707
|
+
/**
|
|
708
|
+
* Test external API integration
|
|
709
|
+
*/
|
|
710
|
+
class Test_External_API extends WP_UnitTestCase {
|
|
711
|
+
|
|
712
|
+
/**
|
|
713
|
+
* Test API call with mock
|
|
714
|
+
*/
|
|
715
|
+
public function test_api_call() {
|
|
716
|
+
// Mock HTTP response
|
|
717
|
+
add_filter( 'pre_http_request', function( $preempt, $args, $url ) {
|
|
718
|
+
if ( strpos( $url, 'api.example.com' ) !== false ) {
|
|
719
|
+
return array(
|
|
720
|
+
'response' => array( 'code' => 200 ),
|
|
721
|
+
'body' => json_encode( array( 'success' => true ) ),
|
|
722
|
+
);
|
|
723
|
+
}
|
|
724
|
+
return $preempt;
|
|
725
|
+
}, 10, 3 );
|
|
726
|
+
|
|
727
|
+
$response = wp_remote_get( 'https://api.example.com/endpoint' );
|
|
728
|
+
$body = json_decode( wp_remote_retrieve_body( $response ), true );
|
|
729
|
+
|
|
730
|
+
$this->assertTrue( $body['success'] );
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
```
|
|
734
|
+
|
|
735
|
+
### Stubbing WordPress Functions
|
|
736
|
+
|
|
737
|
+
```php
|
|
738
|
+
<?php
|
|
739
|
+
/**
|
|
740
|
+
* Test with stubbed functions
|
|
741
|
+
*/
|
|
742
|
+
class Test_Stubbed_Functions extends WP_UnitTestCase {
|
|
743
|
+
|
|
744
|
+
/**
|
|
745
|
+
* Test with stubbed current_time
|
|
746
|
+
*/
|
|
747
|
+
public function test_with_stubbed_time() {
|
|
748
|
+
// Stub current_time to return fixed timestamp
|
|
749
|
+
add_filter( 'current_time', function( $time, $type ) {
|
|
750
|
+
return '2024-01-01 12:00:00';
|
|
751
|
+
}, 10, 2 );
|
|
752
|
+
|
|
753
|
+
$time = current_time( 'mysql' );
|
|
754
|
+
|
|
755
|
+
$this->assertEquals( '2024-01-01 12:00:00', $time );
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
```
|
|
759
|
+
|
|
760
|
+
---
|
|
761
|
+
|
|
762
|
+
## Test Data Factories
|
|
763
|
+
|
|
764
|
+
### Using WordPress Factories
|
|
765
|
+
|
|
766
|
+
```php
|
|
767
|
+
<?php
|
|
768
|
+
/**
|
|
769
|
+
* Test using factories
|
|
770
|
+
*/
|
|
771
|
+
class Test_With_Factories extends WP_UnitTestCase {
|
|
772
|
+
|
|
773
|
+
/**
|
|
774
|
+
* Test create multiple posts
|
|
775
|
+
*/
|
|
776
|
+
public function test_create_posts() {
|
|
777
|
+
$post_ids = $this->factory->post->create_many( 5 );
|
|
778
|
+
|
|
779
|
+
$this->assertCount( 5, $post_ids );
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
/**
|
|
783
|
+
* Test create user with meta
|
|
784
|
+
*/
|
|
785
|
+
public function test_create_user_with_meta() {
|
|
786
|
+
$user_id = $this->factory->user->create( array(
|
|
787
|
+
'role' => 'editor',
|
|
788
|
+
'user_email' => 'test@example.com',
|
|
789
|
+
) );
|
|
790
|
+
|
|
791
|
+
update_user_meta( $user_id, 'custom_field', 'custom value' );
|
|
792
|
+
|
|
793
|
+
$user = get_user_by( 'id', $user_id );
|
|
794
|
+
$custom_field = get_user_meta( $user_id, 'custom_field', true );
|
|
795
|
+
|
|
796
|
+
$this->assertEquals( 'editor', $user->roles[0] );
|
|
797
|
+
$this->assertEquals( 'custom value', $custom_field );
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
/**
|
|
801
|
+
* Test create comment
|
|
802
|
+
*/
|
|
803
|
+
public function test_create_comment() {
|
|
804
|
+
$post_id = $this->factory->post->create();
|
|
805
|
+
$comment_id = $this->factory->comment->create( array(
|
|
806
|
+
'comment_post_ID' => $post_id,
|
|
807
|
+
) );
|
|
808
|
+
|
|
809
|
+
$comment = get_comment( $comment_id );
|
|
810
|
+
|
|
811
|
+
$this->assertEquals( $post_id, $comment->comment_post_ID );
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
```
|
|
815
|
+
|
|
816
|
+
---
|
|
817
|
+
|
|
818
|
+
## Assertion Patterns
|
|
819
|
+
|
|
820
|
+
### Common Assertions
|
|
821
|
+
|
|
822
|
+
```php
|
|
823
|
+
<?php
|
|
824
|
+
/**
|
|
825
|
+
* Common assertion examples
|
|
826
|
+
*/
|
|
827
|
+
class Test_Assertions extends WP_UnitTestCase {
|
|
828
|
+
|
|
829
|
+
/**
|
|
830
|
+
* Test equality assertions
|
|
831
|
+
*/
|
|
832
|
+
public function test_equality() {
|
|
833
|
+
$this->assertEquals( 'expected', 'expected' );
|
|
834
|
+
$this->assertNotEquals( 'expected', 'actual' );
|
|
835
|
+
$this->assertSame( 1, 1 ); // Strict comparison
|
|
836
|
+
$this->assertNotSame( 1, '1' );
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
/**
|
|
840
|
+
* Test boolean assertions
|
|
841
|
+
*/
|
|
842
|
+
public function test_boolean() {
|
|
843
|
+
$this->assertTrue( true );
|
|
844
|
+
$this->assertFalse( false );
|
|
845
|
+
$this->assertNull( null );
|
|
846
|
+
$this->assertNotNull( 'value' );
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
/**
|
|
850
|
+
* Test array assertions
|
|
851
|
+
*/
|
|
852
|
+
public function test_array() {
|
|
853
|
+
$array = array( 'key' => 'value' );
|
|
854
|
+
|
|
855
|
+
$this->assertIsArray( $array );
|
|
856
|
+
$this->assertArrayHasKey( 'key', $array );
|
|
857
|
+
$this->assertContains( 'value', $array );
|
|
858
|
+
$this->assertCount( 1, $array );
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
/**
|
|
862
|
+
* Test string assertions
|
|
863
|
+
*/
|
|
864
|
+
public function test_string() {
|
|
865
|
+
$string = 'Hello World';
|
|
866
|
+
|
|
867
|
+
$this->assertIsString( $string );
|
|
868
|
+
$this->assertStringContainsString( 'World', $string );
|
|
869
|
+
$this->assertStringStartsWith( 'Hello', $string );
|
|
870
|
+
$this->assertStringEndsWith( 'World', $string );
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
/**
|
|
874
|
+
* Test numeric assertions
|
|
875
|
+
*/
|
|
876
|
+
public function test_numeric() {
|
|
877
|
+
$this->assertGreaterThan( 5, 10 );
|
|
878
|
+
$this->assertLessThan( 10, 5 );
|
|
879
|
+
$this->assertGreaterThanOrEqual( 5, 5 );
|
|
880
|
+
$this->assertLessThanOrEqual( 5, 5 );
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
/**
|
|
884
|
+
* Test instance assertions
|
|
885
|
+
*/
|
|
886
|
+
public function test_instance() {
|
|
887
|
+
$post = get_post( $this->factory->post->create() );
|
|
888
|
+
|
|
889
|
+
$this->assertInstanceOf( 'WP_Post', $post );
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
```
|
|
893
|
+
|
|
894
|
+
---
|
|
895
|
+
|
|
896
|
+
## Code Coverage
|
|
897
|
+
|
|
898
|
+
### Generate Coverage Report
|
|
899
|
+
|
|
900
|
+
**composer.json:**
|
|
901
|
+
```json
|
|
902
|
+
{
|
|
903
|
+
"scripts": {
|
|
904
|
+
"test": "phpunit",
|
|
905
|
+
"test:coverage": "phpunit --coverage-html coverage"
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
```
|
|
909
|
+
|
|
910
|
+
**Run coverage:**
|
|
911
|
+
```bash
|
|
912
|
+
composer test:coverage
|
|
913
|
+
```
|
|
914
|
+
|
|
915
|
+
### Coverage Annotations
|
|
916
|
+
|
|
917
|
+
```php
|
|
918
|
+
<?php
|
|
919
|
+
/**
|
|
920
|
+
* Test with coverage annotations
|
|
921
|
+
*/
|
|
922
|
+
class Test_Coverage extends WP_UnitTestCase {
|
|
923
|
+
|
|
924
|
+
/**
|
|
925
|
+
* @covers My_Plugin_Class::method_name
|
|
926
|
+
*/
|
|
927
|
+
public function test_method() {
|
|
928
|
+
$instance = new My_Plugin_Class();
|
|
929
|
+
$result = $instance->method_name();
|
|
930
|
+
|
|
931
|
+
$this->assertTrue( $result );
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
```
|
|
935
|
+
|
|
936
|
+
---
|
|
937
|
+
|
|
938
|
+
## Continuous Integration
|
|
939
|
+
|
|
940
|
+
### GitHub Actions Workflow
|
|
941
|
+
|
|
942
|
+
**.github/workflows/tests.yml:**
|
|
943
|
+
```yaml
|
|
944
|
+
name: Tests
|
|
945
|
+
|
|
946
|
+
on:
|
|
947
|
+
push:
|
|
948
|
+
branches: [ main, develop ]
|
|
949
|
+
pull_request:
|
|
950
|
+
branches: [ main, develop ]
|
|
951
|
+
|
|
952
|
+
jobs:
|
|
953
|
+
test:
|
|
954
|
+
runs-on: ubuntu-latest
|
|
955
|
+
|
|
956
|
+
strategy:
|
|
957
|
+
matrix:
|
|
958
|
+
php: [ '7.4', '8.0', '8.1', '8.2' ]
|
|
959
|
+
wordpress: [ 'latest', '6.0' ]
|
|
960
|
+
|
|
961
|
+
steps:
|
|
962
|
+
- uses: actions/checkout@v3
|
|
963
|
+
|
|
964
|
+
- name: Setup PHP
|
|
965
|
+
uses: shivammathur/setup-php@v2
|
|
966
|
+
with:
|
|
967
|
+
php-version: ${{ matrix.php }}
|
|
968
|
+
extensions: mysqli
|
|
969
|
+
coverage: xdebug
|
|
970
|
+
|
|
971
|
+
- name: Install dependencies
|
|
972
|
+
run: composer install --prefer-dist --no-progress
|
|
973
|
+
|
|
974
|
+
- name: Install WordPress test suite
|
|
975
|
+
run: |
|
|
976
|
+
bash bin/install-wp-tests.sh wordpress_test root root localhost ${{ matrix.wordpress }}
|
|
977
|
+
|
|
978
|
+
- name: Run tests
|
|
979
|
+
run: composer test
|
|
980
|
+
|
|
981
|
+
- name: Upload coverage
|
|
982
|
+
if: matrix.php == '8.1' && matrix.wordpress == 'latest'
|
|
983
|
+
uses: codecov/codecov-action@v3
|
|
984
|
+
```
|
|
985
|
+
|
|
986
|
+
---
|
|
987
|
+
|
|
988
|
+
## Best Practices
|
|
989
|
+
|
|
990
|
+
### DO
|
|
991
|
+
|
|
992
|
+
✅ **Write tests first** (TDD approach when possible)
|
|
993
|
+
✅ **Test one thing per test** method
|
|
994
|
+
✅ **Use descriptive test names** (`test_user_can_save_settings`)
|
|
995
|
+
✅ **Clean up after tests** (use `tearDown()`)
|
|
996
|
+
✅ **Use factories** for test data creation
|
|
997
|
+
✅ **Mock external dependencies** (APIs, services)
|
|
998
|
+
✅ **Test edge cases** and error conditions
|
|
999
|
+
✅ **Aim for high code coverage** (80%+ is good)
|
|
1000
|
+
✅ **Run tests before commits**
|
|
1001
|
+
✅ **Use CI/CD** for automated testing
|
|
1002
|
+
✅ **Test with multiple PHP versions**
|
|
1003
|
+
✅ **Test with multiple WordPress versions**
|
|
1004
|
+
|
|
1005
|
+
### DON'T
|
|
1006
|
+
|
|
1007
|
+
❌ **Don't test WordPress core** functionality
|
|
1008
|
+
❌ **Don't write tests that depend on each other**
|
|
1009
|
+
❌ **Don't use real external APIs** in tests
|
|
1010
|
+
❌ **Don't skip cleanup** in `tearDown()`
|
|
1011
|
+
❌ **Don't test implementation details** - test behavior
|
|
1012
|
+
❌ **Don't ignore failing tests**
|
|
1013
|
+
❌ **Don't commit without running tests**
|
|
1014
|
+
❌ **Don't test private methods** directly
|
|
1015
|
+
❌ **Don't use production database** for tests
|
|
1016
|
+
❌ **Don't hardcode test data** - use factories
|
|
1017
|
+
|
|
1018
|
+
---
|
|
1019
|
+
|
|
1020
|
+
## Test Organization
|
|
1021
|
+
|
|
1022
|
+
### Directory Structure
|
|
1023
|
+
|
|
1024
|
+
```
|
|
1025
|
+
tests/
|
|
1026
|
+
├── bootstrap.php # Test bootstrap
|
|
1027
|
+
├── unit/ # Unit tests
|
|
1028
|
+
│ ├── test-class-plugin.php
|
|
1029
|
+
│ ├── test-functions.php
|
|
1030
|
+
│ └── test-helpers.php
|
|
1031
|
+
├── integration/ # Integration tests
|
|
1032
|
+
│ ├── test-cpt.php
|
|
1033
|
+
│ ├── test-ajax.php
|
|
1034
|
+
│ └── test-rest-api.php
|
|
1035
|
+
├── fixtures/ # Test fixtures
|
|
1036
|
+
│ ├── sample-data.json
|
|
1037
|
+
│ └── sample-image.jpg
|
|
1038
|
+
└── mocks/ # Mock classes
|
|
1039
|
+
└── class-mock-api.php
|
|
1040
|
+
```
|
|
1041
|
+
|
|
1042
|
+
### Naming Conventions
|
|
1043
|
+
|
|
1044
|
+
**Test files:**
|
|
1045
|
+
- Prefix with `test-`
|
|
1046
|
+
- Match source file name: `class-plugin.php` → `test-class-plugin.php`
|
|
1047
|
+
|
|
1048
|
+
**Test classes:**
|
|
1049
|
+
- Prefix with `Test_`
|
|
1050
|
+
- Use underscores: `Test_My_Plugin_Class`
|
|
1051
|
+
|
|
1052
|
+
**Test methods:**
|
|
1053
|
+
- Prefix with `test_`
|
|
1054
|
+
- Descriptive names: `test_user_can_save_settings()`
|
|
1055
|
+
|
|
1056
|
+
---
|
|
1057
|
+
|
|
1058
|
+
## Running Tests
|
|
1059
|
+
|
|
1060
|
+
### Command Line
|
|
1061
|
+
|
|
1062
|
+
```bash
|
|
1063
|
+
# Run all tests
|
|
1064
|
+
phpunit
|
|
1065
|
+
|
|
1066
|
+
# Run specific test suite
|
|
1067
|
+
phpunit --testsuite unit
|
|
1068
|
+
phpunit --testsuite integration
|
|
1069
|
+
|
|
1070
|
+
# Run specific test file
|
|
1071
|
+
phpunit tests/unit/test-class-plugin.php
|
|
1072
|
+
|
|
1073
|
+
# Run specific test method
|
|
1074
|
+
phpunit --filter test_method_name
|
|
1075
|
+
|
|
1076
|
+
# Run with coverage
|
|
1077
|
+
phpunit --coverage-html coverage
|
|
1078
|
+
|
|
1079
|
+
# Run with verbose output
|
|
1080
|
+
phpunit --verbose
|
|
1081
|
+
```
|
|
1082
|
+
|
|
1083
|
+
### Composer Scripts
|
|
1084
|
+
|
|
1085
|
+
**composer.json:**
|
|
1086
|
+
```json
|
|
1087
|
+
{
|
|
1088
|
+
"scripts": {
|
|
1089
|
+
"test": "phpunit",
|
|
1090
|
+
"test:unit": "phpunit --testsuite unit",
|
|
1091
|
+
"test:integration": "phpunit --testsuite integration",
|
|
1092
|
+
"test:coverage": "phpunit --coverage-html coverage",
|
|
1093
|
+
"test:watch": "phpunit-watcher watch"
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
```
|
|
1097
|
+
|
|
1098
|
+
**Run:**
|
|
1099
|
+
```bash
|
|
1100
|
+
composer test
|
|
1101
|
+
composer test:unit
|
|
1102
|
+
composer test:coverage
|
|
1103
|
+
```
|
|
1104
|
+
|
|
1105
|
+
---
|
|
1106
|
+
|
|
1107
|
+
## Debugging Tests
|
|
1108
|
+
|
|
1109
|
+
### Enable Debug Output
|
|
1110
|
+
|
|
1111
|
+
```php
|
|
1112
|
+
<?php
|
|
1113
|
+
/**
|
|
1114
|
+
* Test with debug output
|
|
1115
|
+
*/
|
|
1116
|
+
class Test_Debug extends WP_UnitTestCase {
|
|
1117
|
+
|
|
1118
|
+
public function test_with_debug() {
|
|
1119
|
+
$value = 'test';
|
|
1120
|
+
|
|
1121
|
+
// Output debug information
|
|
1122
|
+
fwrite( STDERR, print_r( $value, true ) );
|
|
1123
|
+
|
|
1124
|
+
$this->assertEquals( 'test', $value );
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
```
|
|
1128
|
+
|
|
1129
|
+
### Use PHPUnit Annotations
|
|
1130
|
+
|
|
1131
|
+
```php
|
|
1132
|
+
<?php
|
|
1133
|
+
/**
|
|
1134
|
+
* @group slow
|
|
1135
|
+
* @group external-api
|
|
1136
|
+
*/
|
|
1137
|
+
class Test_External_API extends WP_UnitTestCase {
|
|
1138
|
+
|
|
1139
|
+
/**
|
|
1140
|
+
* @group integration
|
|
1141
|
+
*/
|
|
1142
|
+
public function test_api_call() {
|
|
1143
|
+
// Test code
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
```
|
|
1147
|
+
|
|
1148
|
+
**Run specific groups:**
|
|
1149
|
+
```bash
|
|
1150
|
+
phpunit --group integration
|
|
1151
|
+
phpunit --exclude-group slow
|
|
1152
|
+
```
|
|
1153
|
+
|
|
1154
|
+
---
|
|
1155
|
+
|
|
1156
|
+
## Resources
|
|
1157
|
+
|
|
1158
|
+
- [WordPress PHPUnit Documentation](https://make.wordpress.org/core/handbook/testing/automated-testing/phpunit/)
|
|
1159
|
+
- [PHPUnit Documentation](https://phpunit.de/documentation.html)
|
|
1160
|
+
- [WP_UnitTestCase Reference](https://developer.wordpress.org/reference/classes/wp_unittestcase/)
|
|
1161
|
+
- [WordPress Test Suite](https://develop.svn.wordpress.org/trunk/tests/phpunit/)
|
|
1162
|
+
- [WooCommerce Testing](https://github.com/woocommerce/woocommerce/tree/trunk/plugins/woocommerce/tests)
|
|
1163
|
+
```
|
|
1164
|
+
|
|
1165
|
+
|