Automated Hook Tests with WP_UnitTestCase
Hook behavior should be verified with repeatable assertions, not inferred from manual editing only.
You will write unit and integration tests for ACF value filters and acf/save_post synchronization routines.
Concept Overview
ACF hook tests usually fall into two categories: transformation tests (acf/update_value, acf/format_value) and workflow tests (acf/save_post, relation sync, derived metadata updates). WP_UnitTestCase helps build realistic WordPress context while keeping assertions deterministic.
For every business-critical hook, define input, lifecycle stage, and exact expected output in one test method.
Why It Matters
Untested hooks can silently mutate stored data and break templates days after release.
| Approach | What Happens | Business Impact |
|---|---|---|
| Test hook callbacks with deterministic fixtures | Behavior remains stable across refactors | Safer long-term maintenance |
| Validate only by clicking admin UI | Regressions are discovered late | Higher release risk |
Add integration tests for acf/save_post flows | Cross-record sync issues are caught early | Fewer data integrity incidents |
Reference Table
| Term | Meaning | Where You Use It | Fast Check |
|---|---|---|---|
| WP_UnitTestCase | WordPress testing base class | Unit and integration tests in plugin/theme repos | Test class boots WP context correctly |
| Hook Assertion | Test verifying callback output or side effects | Value filters and save automation | Assertion checks exact expected value |
| Integration Fixture | Seeded posts/users/terms for workflow tests | acf/save_post and relation sync | Fixture IDs are deterministic in test scope |
| Regression Test | Test written after bug discovery | Prevent repeat failures in future releases | Test fails on old bug behavior |
Practical Use Cases
Use Case 1: Unit test for acf/update_value phone normalization
A sales contact field must always be saved in E.164 format.
- Register normalization filter in test bootstrap.
- Apply the filter with malformed phone input.
- Assert normalized value exactly matches expected format.
- Add one regression case for already normalized input.
Validate this state through code and CLI checks: Show CI test output with test_sales_phone_normalization passing.
<?php
class Test_Sales_Phone_Hook extends WP_UnitTestCase {
public function test_sales_phone_normalization() {
$result = apply_filters(
'acf/update_value/name=sales_phone',
'(65) 6700-8899',
101,
['name' => 'sales_phone']
);
$this->assertSame('+6567008899', $result);
}
}
?>
OK (1 test, 1 assertion)
Use Case 2: Integration test for bidirectional relationship sync
Event saves should update linked speaker records through acf/save_post automation.
- Seed an
eventpost and twospeakerposts in test setup. - Update event relationship field with selected speaker IDs.
- Trigger
acf/save_postand fetch reverse field from each speaker. - Assert event ID appears exactly once in reverse arrays.
Validate this state through code and CLI checks: Capture test report showing relation sync assertion counts.
<?php
class Test_Event_Speaker_Sync extends WP_UnitTestCase {
public function test_event_syncs_to_speaker_reverse_field() {
$event_id = self::factory()->post->create(['post_type' => 'event']);
$speaker_id = self::factory()->post->create(['post_type' => 'speaker']);
update_field('event_speakers', [$speaker_id], $event_id);
do_action('acf/save_post', $event_id);
$reverse = get_field('speaker_events', $speaker_id) ?: [];
$this->assertContains($event_id, $reverse);
}
}
?>
OK (1 test, 2 assertions)
Common Mistakes
| Mistake | What Happens | Better Approach |
|---|---|---|
| Testing only return values, not side effects | Sync and persistence bugs go undetected | Add assertions for stored fields and related records |
| Sharing mutable fixtures across tests | Tests fail intermittently in CI | Isolate fixtures per test method or reset state |
| Ignoring edge cases after bug fixes | Same incidents recur in later releases | Add regression tests immediately after every fix |
Best Practices
- Keep hook callbacks pure where possible to simplify assertions.
- Use clear test method names that describe expected business behavior.
- Include edge-case tests for null, legacy, and malformed values.
- Run hook test suites on every pull request.
- Treat failing hook tests as release blockers for affected modules.
Hands-On Practice
- Write one unit test for an
acf/update_valuefilter in your project. - Add one integration test for
acf/save_postside effects on related records. - Create a regression test for a previously fixed ACF bug.
What's Next
- Continue to CI/CD Pipeline Quality Gates and Release Checks.
- Review this module context in Module 9 Overview.
- Related lesson: Bidirectional Relationships with acf/save_post.