Commit 7c029e63741 for woocommerce

commit 7c029e63741bffd775b0f387720b47fc6def3703
Author: Vlad Olaru <vlad.olaru@automattic.com>
Date:   Sat Aug 1 13:32:27 2026 +0300

    [tests] Fix three test hygiene defects found while sweeping lifecycle methods (#67298)

    * fix: guard the fake WC Calypso Bridge function declarations in tests

    `WC_Admin_Tests_RemoteSpecs_RuleProcessors_IsWooExpressRuleProcessor`
    declares two stand-ins for functions the WC Calypso Bridge plugin
    provides, so the rule processor has something to call. Declaring a
    function is irreversible and process-wide, and neither declaration was
    guarded.

    That makes a redeclaration fatal rather than merely untidy. The
    `wc_calypso_bridge_is_woo_express_trial_plan` one is declared inside
    `test_is_trial_plan`, so adding a data provider to that test — an
    ordinary thing to do — would run the declaration more than once and kill
    the process. Both would also collide with the real plugin if it were ever
    loaded in the same run.

    Both are now behind `function_exists()`.

    * fix: run the LegacyProxy get_global test

    `get_global_can_be_used_to_get_the_value_of_a_global` has no `test_`
    prefix and no `@test` annotation, so PHPUnit has never collected it. It
    carries a `@testdox` line, which is only a display label and has no
    bearing on collection.

    Nothing suggests that was deliberate. It arrived in #33140, the same
    change that introduced `LegacyProxy::get_global()`, and has not been
    touched since. Every one of the eight other tests in the file is
    prefixed, and the same commit added three correctly prefixed tests to
    `MockableLegacyProxyTest` — including the mockable counterpart of this
    one. A test that is never collected produces no output at all, which is
    why it went unnoticed for three and a half years.

    The consequence is that `LegacyProxy::get_global()` has had no direct
    coverage since it was added; only the mockable subclass is exercised. The
    test passes once collected.

    * fix: discard the Spy_REST_Server installed by the Store API controller tests

    `ControllerTests::setUp()` replaces the `$wp_rest_server` global with a
    `Spy_REST_Server`, and the class had no teardown. `$wp_rest_server` is
    not among the globals WordPress resets between tests, so the spy stayed
    in place for the remainder of the process and `rest_get_server()` kept
    handing it out to every later test.

    Nulling it in `tearDown()` lets the next caller build a fresh server.

    * chore: add changelog for the test hygiene follow-ups

    * fix: discard Spy REST servers after tests

    These tests replace the global REST server with a Spy_REST_Server but previously left it installed after completion. The shared global could leak routes or captured state into later tests, making results depend on execution order.

    Clear the global in each class's teardown while retaining the existing parent and class-specific cleanup.

    * fix: restore ObjectCache failure test

    An ObjectCache test lacked PHPUnit's test prefix, so the cache-engine failure path was not covered. Collecting it exposed a stale fixture reference left behind when these tests moved to WPCacheEngine.

    Use a test-local mocked CacheEngine with a fresh ObjectCache instance so the original false-result assertion exercises the intended failure path without changing the shared fixture.

    * fix: make RateLimits teardown public

    The newly added RateLimits teardown used protected visibility while WooCommerce test lifecycle methods conventionally expose their setup and cleanup hooks publicly.

    Widen only the method visibility, preserving the REST server cleanup and parent teardown call unchanged.

diff --git a/plugins/woocommerce/changelog/fix-test-hygiene-followups b/plugins/woocommerce/changelog/fix-test-hygiene-followups
new file mode 100644
index 00000000000..1306fe9ff87
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-test-hygiene-followups
@@ -0,0 +1,4 @@
+Significance: patch
+Type: dev
+
+Guard the fake WC Calypso Bridge functions declared in tests, collect the LegacyProxy get_global test that was never running, and reset the REST server global the Store API controller tests replace.
diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/is-woo-express-rule-processor.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/is-woo-express-rule-processor.php
index 8c838e08890..a1071d56172 100644
--- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/is-woo-express-rule-processor.php
+++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/remote-specs/rule-processors/is-woo-express-rule-processor.php
@@ -19,11 +19,16 @@ class WC_Admin_Tests_RemoteSpecs_RuleProcessors_IsWooExpressRuleProcessor extend
 	public static function setUpBeforeClass(): void {
 		parent::setUpBeforeClass();

-		/**
-		 * Fake function wc_calypso_bridge_is_woo_express_plan so that we can test the processor.
-		 */
-		function wc_calypso_bridge_is_woo_express_plan() {
-			return apply_filters( 'test_wc_calypso_bridge_is_woo_express_plan', true ); // phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
+		// Declaring a function is irreversible and process-wide, so guard it: without this a
+		// second declaration is a fatal error, whether it comes from this class running twice
+		// in a process or from the real WC Calypso Bridge plugin being loaded alongside it.
+		if ( ! function_exists( 'wc_calypso_bridge_is_woo_express_plan' ) ) {
+			/**
+			 * Fake function wc_calypso_bridge_is_woo_express_plan so that we can test the processor.
+			 */
+			function wc_calypso_bridge_is_woo_express_plan() {
+				return apply_filters( 'test_wc_calypso_bridge_is_woo_express_plan', true ); // phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
+			}
 		}
 	}

@@ -97,9 +102,13 @@ class WC_Admin_Tests_RemoteSpecs_RuleProcessors_IsWooExpressRuleProcessor extend
 	 * @group fast
 	 */
 	public function test_is_trial_plan() {
-		/** Fake function wc_calypso_bridge_is_woo_express_trial_plan. */
-		function wc_calypso_bridge_is_woo_express_trial_plan() {
-			return true;
+		// Guarded because this one sits in a test method: adding a data provider here would
+		// run it more than once and the redeclaration would be fatal.
+		if ( ! function_exists( 'wc_calypso_bridge_is_woo_express_trial_plan' ) ) {
+			/** Fake function wc_calypso_bridge_is_woo_express_trial_plan. */
+			function wc_calypso_bridge_is_woo_express_trial_plan() {
+				return true;
+			}
 		}

 		$processor = new IsWooExpressRuleProcessor();
diff --git a/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/OrderConfirmation/Totals.php b/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/OrderConfirmation/Totals.php
index 64ac1528a3a..e0f38aef1d8 100644
--- a/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/OrderConfirmation/Totals.php
+++ b/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/OrderConfirmation/Totals.php
@@ -100,6 +100,9 @@ class Totals extends \WP_UnitTestCase {
 	 * tearDown.
 	 */
 	public function tearDown(): void {
+		global $wp_rest_server;
+		$wp_rest_server = null;
+
 		parent::tearDown();
 		remove_filter( 'woocommerce_set_cookie_enabled', array( $this, 'filter_woocommerce_set_cookie_enabled' ) );
 		WC()->cart->empty_cart();
diff --git a/plugins/woocommerce/tests/php/src/Blocks/StoreApi/ControllerTests.php b/plugins/woocommerce/tests/php/src/Blocks/StoreApi/ControllerTests.php
index f69b046c56d..b02250a400f 100644
--- a/plugins/woocommerce/tests/php/src/Blocks/StoreApi/ControllerTests.php
+++ b/plugins/woocommerce/tests/php/src/Blocks/StoreApi/ControllerTests.php
@@ -21,6 +21,21 @@ class ControllerTests extends \WP_Test_REST_TestCase {
 		do_action( 'rest_api_init', $wp_rest_server );
 	}

+	/**
+	 * Discard the Spy_REST_Server this class installed.
+	 *
+	 * `$wp_rest_server` is not one of the globals WordPress resets between tests, so without
+	 * this the spy stays in place for every test that runs afterwards in the same process,
+	 * and `rest_get_server()` keeps handing it out. Nulling it lets the next caller build a
+	 * fresh server.
+	 */
+	protected function tearDown(): void {
+		global $wp_rest_server;
+		$wp_rest_server = null;
+
+		parent::tearDown();
+	}
+
 	/**
 	 * Test v1 route registration.
 	 */
diff --git a/plugins/woocommerce/tests/php/src/Blocks/StoreApi/RateLimitsTests.php b/plugins/woocommerce/tests/php/src/Blocks/StoreApi/RateLimitsTests.php
index c1bd611929a..38c041f40e7 100644
--- a/plugins/woocommerce/tests/php/src/Blocks/StoreApi/RateLimitsTests.php
+++ b/plugins/woocommerce/tests/php/src/Blocks/StoreApi/RateLimitsTests.php
@@ -33,6 +33,16 @@ class RateLimitsTests extends WP_Test_REST_TestCase {
 		do_action( 'rest_api_init', $wp_rest_server );
 	}

+	/**
+	 * Discard the REST server installed for the test.
+	 */
+	public function tearDown(): void {
+		global $wp_rest_server;
+		$wp_rest_server = null;
+
+		parent::tearDown();
+	}
+
 	/**
 	 * Tests that Rate limiting headers are sent and set correctly when Rate Limiting
 	 * main functionality is enabled.
diff --git a/plugins/woocommerce/tests/php/src/Caching/ObjectCacheTest.php b/plugins/woocommerce/tests/php/src/Caching/ObjectCacheTest.php
index b2bde1c0623..5630b8d3372 100644
--- a/plugins/woocommerce/tests/php/src/Caching/ObjectCacheTest.php
+++ b/plugins/woocommerce/tests/php/src/Caching/ObjectCacheTest.php
@@ -137,10 +137,34 @@ class ObjectCacheTest extends \WC_Unit_Test_Case {
 	/**
 	 * @testdox 'set' returns false if the cache engine's caching method fails.
 	 */
-	public function try_set_when_cache_engine_fails() {
-		$this->cache_engine->caching_succeeds = false;
+	public function test_try_set_when_cache_engine_fails() {
+		$cache_engine = $this->createMock( CacheEngine::class );
+		$cache_engine->method( 'cache_object' )->willReturn( false );

-		$result = $this->sut->set( array( 'foo' ), 'the_id' );
+		add_filter(
+			'wc_object_cache_get_engine',
+			function () use ( $cache_engine ) {
+				return $cache_engine;
+			}
+		);
+
+		// phpcs:disable Squiz.Commenting
+		$sut = new class() extends ObjectCache {
+			public function get_object_type(): string {
+				return 'the_type';
+			}
+
+			protected function get_object_id( $value ) {
+				return null;
+			}
+
+			protected function validate( $value ): ?array {
+				return null;
+			}
+		};
+		// phpcs:enable Squiz.Commenting
+
+		$result = $sut->set( array( 'foo' ), 'the_id' );
 		$this->assertFalse( $result );
 	}

diff --git a/plugins/woocommerce/tests/php/src/Proxies/LegacyProxyTest.php b/plugins/woocommerce/tests/php/src/Proxies/LegacyProxyTest.php
index 5d74a6bbf63..8347b438808 100644
--- a/plugins/woocommerce/tests/php/src/Proxies/LegacyProxyTest.php
+++ b/plugins/woocommerce/tests/php/src/Proxies/LegacyProxyTest.php
@@ -110,7 +110,7 @@ class LegacyProxyTest extends \WC_Unit_Test_Case {
 	/**
 	 * @testdox 'get_global' can be used to get the value of a global.
 	 */
-	public function get_global_can_be_used_to_get_the_value_of_a_global() {
+	public function test_get_global_can_be_used_to_get_the_value_of_a_global() {
 		global $wpdb;

 		$result = $this->sut->get_global( 'wpdb' );