Commit cafe00b2a15 for woocommerce
commit cafe00b2a15094b338692c1989972cdeacf19a78
Author: Vlad Olaru <vlad.olaru@automattic.com>
Date: Tue Oct 13 14:43:03 2026 +0300
[tests] Fix 14 order-dependent PHPUnit failures and 6 latent state leaks (#69117)
Several PHPUnit tests passed or failed depending on which tests ran
before them, because they changed process state that neither the
per-test database rollback nor WP_UnitTestCase's hook restore resets.
Each fix restores that state in the class that changes it.
WC_Unit_Test_Case is unchanged; #69738 proposes moving the shared
restores into it.
Order-dependent failures fixed:
- Block types: MiniCartFooterBlock puts the original footer block back,
and the Add to Cart with Options mock blocks register through one
helper, AddToCartWithOptionsMocks, which skips any already
registered.
- Shared caches: ProductFilterTaxonomyTest clears the taxonomy
hierarchy map in setUp() and tearDown(); ProductsStore resets its
statics before each test.
- ShippingControllerTest moves to WC_Unit_Test_Case, resets
WC_Shipping's method list and the dynamic WC()->shipping property,
and reloads the shipping methods in setUp() so local pickup is
registered once the checkout is a block.
- Customer address: five classes put back the shipping address they
change, and OrderControllerTests sets the US/CA location its
invalid-address tests need for a flat rate.
- REQUEST_URI: ProductPageIntegrationTest restores what go_to() sets,
which made WC_Cart_Test think the shopper was on its product page.
- OrderItemSchemaTest registers pa_size itself and undoes that
registration and the $wc_product_attributes global afterwards.
- LegacySelect2UsageTrackerTest accepts the checkout page type once
WOOCOMMERCE_CHECKOUT is defined, and WC_Tests_Coupon sets it through
Constants instead of define().
Latent leaks closed: Totals and RateLimitsTests move to
WC_Unit_Test_Case for its cart_context reset; BlockTypesController and
Blocks\Checkout unregister their test block types; the V4 shipping zone
method tests unload their custom method; the Select2 tests dequeue
through WP_Dependencies; and the PHP_OS and WCPAY_VERSION_NUMBER
overrides are cleared.
No production code changes. The whole suite passes in default order
(17097 tests), and each fix's pair fails on trunk and passes here.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
diff --git a/plugins/woocommerce/changelog/fix-test-order-shared-state-leaks b/plugins/woocommerce/changelog/fix-test-order-shared-state-leaks
new file mode 100644
index 00000000000..93d9a4aab3f
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-test-order-shared-state-leaks
@@ -0,0 +1,3 @@
+Significance: patch
+Type: dev
+Comment: Make PHPUnit tests restore shared state they change so they pass in any order; no production change.
diff --git a/plugins/woocommerce/tests/legacy/unit-tests/coupon/coupon.php b/plugins/woocommerce/tests/legacy/unit-tests/coupon/coupon.php
index e6fd2127c68..a668f27cf86 100644
--- a/plugins/woocommerce/tests/legacy/unit-tests/coupon/coupon.php
+++ b/plugins/woocommerce/tests/legacy/unit-tests/coupon/coupon.php
@@ -12,12 +12,22 @@
*/
class WC_Tests_Coupon extends WC_Unit_Test_Case {
+ /**
+ * Customer shipping address before the test, restored at teardown.
+ *
+ * @var array<string, string>
+ */
+ private $previous_shipping_address = array();
+
/**
* Sets up the test class.
*/
public function setUp(): void {
parent::setUp();
+ // WC()->customer outlives a test, so keep its location to restore at teardown.
+ $this->previous_shipping_address = WC()->customer->get_shipping( 'edit' );
+
// Set a valid address for the customer so shipping rates will calculate.
WC()->customer->set_shipping_country( 'US' );
WC()->customer->set_shipping_state( 'NY' );
@@ -28,7 +38,10 @@ class WC_Tests_Coupon extends WC_Unit_Test_Case {
* Cleans up after the test class.
*/
public function tearDown(): void {
- WC()->cart->remove_coupons();
+ \Automattic\Jetpack\Constants::clear_single_constant( 'WOOCOMMERCE_CHECKOUT' );
+ foreach ( $this->previous_shipping_address as $key => $value ) {
+ WC()->customer->{"set_shipping_{$key}"}( $value );
+ }
parent::tearDown();
}
@@ -301,10 +314,9 @@ class WC_Tests_Coupon extends WC_Unit_Test_Case {
)
);
- // We need this to have the calculate_totals() method calculate totals.
- if ( ! defined( 'WOOCOMMERCE_CHECKOUT' ) ) {
- define( 'WOOCOMMERCE_CHECKOUT', true );
- }
+ // We need this to have the calculate_totals() method calculate totals. Set it through
+ // Constants rather than define(), which would stay defined for every later test.
+ \Automattic\Jetpack\Constants::set_constant( 'WOOCOMMERCE_CHECKOUT', true );
// Add 2 products and coupon to cart.
WC()->cart->add_to_cart( $product->get_id(), 2 );
diff --git a/plugins/woocommerce/tests/php/includes/class-wc-cart-default-shipping-method-test.php b/plugins/woocommerce/tests/php/includes/class-wc-cart-default-shipping-method-test.php
index df411075f05..56d33cf9060 100644
--- a/plugins/woocommerce/tests/php/includes/class-wc-cart-default-shipping-method-test.php
+++ b/plugins/woocommerce/tests/php/includes/class-wc-cart-default-shipping-method-test.php
@@ -13,11 +13,11 @@ declare( strict_types = 1 );
class WC_Cart_Default_Shipping_Method_Test extends WC_Unit_Test_Case {
/**
- * Shipping zone used across tests.
+ * Customer shipping address before the test, restored at teardown.
*
- * @var WC_Shipping_Zone
+ * @var array<string, string>
*/
- private $zone;
+ private $previous_shipping_address = array();
/**
* Set up test fixtures.
@@ -26,10 +26,10 @@ class WC_Cart_Default_Shipping_Method_Test extends WC_Unit_Test_Case {
parent::setUp();
// Create a shipping zone with a flat rate so CartCheckoutUtils::shipping_methods_exist() returns true.
- $this->zone = new WC_Shipping_Zone();
- $this->zone->set_zone_name( 'Test Zone' );
- $this->zone->save();
- $this->zone->add_shipping_method( 'flat_rate' );
+ $zone = new WC_Shipping_Zone();
+ $zone->set_zone_name( 'Test Zone' );
+ $zone->save();
+ $zone->add_shipping_method( 'flat_rate' );
// Flush the shipping method count transient so the new zone is picked up.
WC_Cache_Helper::get_transient_version( 'shipping', true );
@@ -37,15 +37,18 @@ class WC_Cart_Default_Shipping_Method_Test extends WC_Unit_Test_Case {
// Set block checkout context (not shortcode).
WC()->cart->cart_context = 'store-api';
+
+ // Tests clear or set the shipping address on WC()->customer, which outlives a test.
+ $this->previous_shipping_address = WC()->customer->get_shipping( 'edit' );
}
/**
* Tear down test fixtures.
*/
public function tearDown(): void {
- $this->zone->delete( true );
- update_option( 'woocommerce_shipping_cost_requires_address', 'no' );
- WC()->cart->cart_context = 'shortcode';
+ foreach ( $this->previous_shipping_address as $key => $value ) {
+ WC()->customer->{"set_shipping_{$key}"}( $value );
+ }
parent::tearDown();
}
diff --git a/plugins/woocommerce/tests/php/includes/class-wc-cart-test.php b/plugins/woocommerce/tests/php/includes/class-wc-cart-test.php
index 187f0a80009..cf62b8a466d 100644
--- a/plugins/woocommerce/tests/php/includes/class-wc-cart-test.php
+++ b/plugins/woocommerce/tests/php/includes/class-wc-cart-test.php
@@ -22,11 +22,11 @@ class WC_Cart_Test extends \WC_Unit_Test_Case {
protected $add_to_cart_quantity_filter_args = array();
/**
- * Customer shipping address to restore after a test that changes it, keyed by prop name.
+ * Customer shipping address before the test, restored at teardown.
*
- * @var array<string, string>|null
+ * @var array<string, string>
*/
- private $original_shipping_address = null;
+ private $previous_shipping_address = array();
/**
* Called before every test.
@@ -35,6 +35,9 @@ class WC_Cart_Test extends \WC_Unit_Test_Case {
parent::setUp();
$fixtures = new FixtureData();
$fixtures->shipping_add_flat_rate();
+
+ // Several tests change the shipping address on WC()->customer, which outlives a test.
+ $this->previous_shipping_address = WC()->customer->get_shipping( 'edit' );
}
/**
@@ -44,14 +47,13 @@ class WC_Cart_Test extends \WC_Unit_Test_Case {
parent::tearDown();
WC()->customer->set_is_vat_exempt( false );
+ foreach ( $this->previous_shipping_address as $key => $value ) {
+ WC()->customer->{"set_shipping_{$key}"}( $value );
+ }
WC()->session->set( 'wc_notices', null );
- if ( null !== $this->original_shipping_address ) {
- WC()->customer->set_props( $this->original_shipping_address );
- $this->original_shipping_address = null;
- // The checkout fields were built while the test's filters were attached.
- $this->clear_checkout_fields();
- }
+ // The checkout fields may have been built while a test's filters were attached.
+ $this->clear_checkout_fields();
// The parent teardown only clears chosen_shipping_methods, through
// WC_Shipping::reset_shipping(). Planted shipping_for_package_* rates survive and
@@ -841,6 +843,9 @@ class WC_Cart_Test extends \WC_Unit_Test_Case {
* Test show shipping.
*/
public function test_show_shipping() {
+ // Start from a customer without an address; WC()->customer outlives a test.
+ WC()->customer->set_shipping_location( '', '', '', '' );
+
// Test with an empty cart.
$this->assertFalse( WC()->cart->show_shipping() );
@@ -1382,13 +1387,7 @@ class WC_Cart_Test extends \WC_Unit_Test_Case {
update_option( 'woocommerce_shipping_cost_requires_address', 'yes' );
$product = WC_Helper_Product::create_simple_product();
WC()->cart->add_to_cart( $product->get_id(), 1 );
- $customer = WC()->cart->get_customer();
- $this->original_shipping_address = array(
- 'shipping_country' => $customer->get_shipping_country(),
- 'shipping_state' => $customer->get_shipping_state(),
- 'shipping_city' => $customer->get_shipping_city(),
- 'shipping_postcode' => $customer->get_shipping_postcode(),
- );
+ $customer = WC()->cart->get_customer();
$customer->set_shipping_country( 'US' );
$customer->set_shipping_city( 'New York' );
$customer->set_shipping_state( 'state' === $missing_field ? '' : 'NY' );
diff --git a/plugins/woocommerce/tests/php/includes/class-wc-product-downloads-test.php b/plugins/woocommerce/tests/php/includes/class-wc-product-downloads-test.php
index 2c3727508b1..601266010c2 100644
--- a/plugins/woocommerce/tests/php/includes/class-wc-product-downloads-test.php
+++ b/plugins/woocommerce/tests/php/includes/class-wc-product-downloads-test.php
@@ -39,6 +39,7 @@ class WC_Product_Download_Test extends WC_Unit_Test_Case {
$download->set_file( $file_path_with_period_at_end );
$this->assertEquals( false, $download->is_allowed_filetype() );
} finally {
+ \Automattic\Jetpack\Constants::clear_single_constant( 'PHP_OS' );
$filesystem->delete( $file_path_with_period_at_end );
}
}
diff --git a/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version4/ShippingZoneMethod/class-wc-rest-shipping-zone-method-v4-controller-tests.php b/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version4/ShippingZoneMethod/class-wc-rest-shipping-zone-method-v4-controller-tests.php
index 56dbbf48428..6be6c5e344f 100644
--- a/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version4/ShippingZoneMethod/class-wc-rest-shipping-zone-method-v4-controller-tests.php
+++ b/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version4/ShippingZoneMethod/class-wc-rest-shipping-zone-method-v4-controller-tests.php
@@ -82,6 +82,10 @@ class WC_REST_Shipping_Zone_Method_V4_Controller_Tests extends WC_REST_Unit_Test
}
$this->created_zones = array();
+ // A test loads a custom method into the shared WC_Shipping list. The hook restore drops
+ // its filter but not the loaded list, so reset the list to load again on next use.
+ WC()->shipping()->unregister_shipping_methods();
+
parent::tearDown();
}
diff --git a/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/AddToCartWithOptions.php b/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/AddToCartWithOptions.php
index 7b22bf2ec6c..adee11bb09a 100644
--- a/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/AddToCartWithOptions.php
+++ b/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/AddToCartWithOptions.php
@@ -7,14 +7,7 @@ namespace Automattic\WooCommerce\Tests\Blocks\BlockTypes;
use Automattic\WooCommerce\Enums\ProductStockStatus;
use Automattic\WooCommerce\Tests\Blocks\Utils\WC_Product_Custom;
use Automattic\WooCommerce\Tests\Blocks\Helpers\FixtureData;
-use Automattic\WooCommerce\Tests\Blocks\Mocks\AddToCartWithOptionsMock;
-use Automattic\WooCommerce\Tests\Blocks\Mocks\AddToCartWithOptionsQuantitySelectorMock;
-use Automattic\WooCommerce\Tests\Blocks\Mocks\AddToCartWithOptionsGroupedProductSelectorMock;
-use Automattic\WooCommerce\Tests\Blocks\Mocks\AddToCartWithOptionsGroupedProductItemMock;
-use Automattic\WooCommerce\Tests\Blocks\Mocks\AddToCartWithOptionsGroupedProductItemSelectorMock;
-use Automattic\WooCommerce\Tests\Blocks\Mocks\AddToCartWithOptionsVariationSelectorMock;
-use Automattic\WooCommerce\Tests\Blocks\Mocks\AddToCartWithOptionsVariationSelectorAttributeMock;
-use Automattic\WooCommerce\Tests\Blocks\Mocks\AddToCartWithOptionsVariationSelectorAttributeNameMock;
+use Automattic\WooCommerce\Tests\Blocks\Mocks\AddToCartWithOptionsMocks;
use Automattic\WooCommerce\Blocks\BlockTypes\AddToCartWithOptions\Utils;
use Automattic\WooCommerce\Internal\Features\FeaturesController;
@@ -23,33 +16,13 @@ use Automattic\WooCommerce\Internal\Features\FeaturesController;
*/
class AddToCartWithOptions extends \WP_UnitTestCase {
- /**
- * Tracks whether blocks have been registered.
- *
- * @var bool
- */
- protected static $are_blocks_registered = false;
-
/**
* Initiate the mock object.
*/
protected function setUp(): void {
parent::setUp();
- if ( ! self::$are_blocks_registered ) {
- // We need to register the blocks after set up. They are no registered
- // on `init` because `init` is called with a classic theme.
- new AddToCartWithOptionsMock();
- new AddToCartWithOptionsQuantitySelectorMock();
- new AddToCartWithOptionsGroupedProductSelectorMock();
- new AddToCartWithOptionsGroupedProductItemMock();
- new AddToCartWithOptionsGroupedProductItemSelectorMock();
- new AddToCartWithOptionsVariationSelectorMock();
- new AddToCartWithOptionsVariationSelectorAttributeMock();
- new AddToCartWithOptionsVariationSelectorAttributeNameMock();
-
- self::$are_blocks_registered = true;
- }
+ AddToCartWithOptionsMocks::register_all();
}
/**
diff --git a/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/Checkout.php b/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/Checkout.php
index e2c1b7ce53e..1a48218a05b 100644
--- a/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/Checkout.php
+++ b/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/Checkout.php
@@ -64,6 +64,11 @@ class Checkout extends \WP_UnitTestCase {
protected function tearDown(): void {
parent::tearDown();
remove_filter( 'woocommerce_logging_class', array( $this, 'override_wc_logger' ) );
+
+ // A test registers a CheckoutMock block type, and the block type registry outlives a test.
+ if ( \WP_Block_Type_Registry::get_instance()->is_registered( 'woocommerce/checkout-mock' ) ) {
+ \WP_Block_Type_Registry::get_instance()->unregister( 'woocommerce/checkout-mock' );
+ }
}
/**
diff --git a/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/MiniCartFooterBlock.php b/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/MiniCartFooterBlock.php
index 78bb1208e04..8fb7cf44b8f 100644
--- a/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/MiniCartFooterBlock.php
+++ b/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/MiniCartFooterBlock.php
@@ -22,6 +22,13 @@ class MiniCartFooterBlock extends \WP_UnitTestCase {
*/
protected $block;
+ /**
+ * The original block type registry entry for the block.
+ *
+ * @var \WP_Block_Type|null
+ */
+ private $original_block_type;
+
/**
* Setup test.
*/
@@ -29,7 +36,9 @@ class MiniCartFooterBlock extends \WP_UnitTestCase {
parent::setUp();
$registry = \WP_Block_Type_Registry::get_instance();
- if ( $registry->is_registered( 'woocommerce/mini-cart-footer-block' ) ) {
+
+ $this->original_block_type = $registry->get_registered( 'woocommerce/mini-cart-footer-block' );
+ if ( $this->original_block_type ) {
$registry->unregister( 'woocommerce/mini-cart-footer-block' );
}
@@ -48,6 +57,9 @@ class MiniCartFooterBlock extends \WP_UnitTestCase {
if ( $registry->is_registered( 'woocommerce/mini-cart-footer-block' ) ) {
$registry->unregister( 'woocommerce/mini-cart-footer-block' );
}
+ if ( $this->original_block_type ) {
+ $registry->register( $this->original_block_type );
+ }
unset( $this->block );
parent::tearDown();
}
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 e1b98efe153..4ce2eab5674 100644
--- a/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/OrderConfirmation/Totals.php
+++ b/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/OrderConfirmation/Totals.php
@@ -19,11 +19,11 @@ use Automattic\WooCommerce\Enums\ProductStockStatus;
*
* @since $VID:$
*/
-class Totals extends \WP_UnitTestCase {
+class Totals extends \WC_Unit_Test_Case {
/**
* Setup test product data. Called before every test.
*/
- protected function setUp(): void {
+ public function setUp(): void {
parent::setUp();
add_filter( 'woocommerce_set_cookie_enabled', array( $this, 'filter_woocommerce_set_cookie_enabled' ), 10, 4 );
@@ -102,10 +102,12 @@ class Totals extends \WP_UnitTestCase {
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();
- WC()->session->destroy_session();
+ try {
+ // The session handler deletes its row through $wpdb, so this has to run before the rollback.
+ WC()->session->destroy_session();
+ } finally {
+ parent::tearDown();
+ }
}
/**
diff --git a/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/ProductFilterTaxonomyTest.php b/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/ProductFilterTaxonomyTest.php
index d989be19369..6615916479e 100644
--- a/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/ProductFilterTaxonomyTest.php
+++ b/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/ProductFilterTaxonomyTest.php
@@ -3,6 +3,7 @@ declare( strict_types = 1 );
namespace Automattic\WooCommerce\Tests\Blocks\BlockTypes;
+use Automattic\WooCommerce\Internal\ProductFilters\TaxonomyHierarchyData;
use WC_Unit_Test_Case;
/**
@@ -30,6 +31,11 @@ class ProductFilterTaxonomyTest extends WC_Unit_Test_Case {
public function setUp(): void {
parent::setUp();
+ // The container keeps the product_cat hierarchy map across tests, and nothing here clears it when
+ // terms are created or deleted. Clear it before and after, so this class neither reads a stale
+ // map nor leaves its own behind.
+ wc_get_container()->get( TaxonomyHierarchyData::class )->clear_cache( 'product_cat' );
+
add_filter( 'woocommerce_pre_product_filter_data', array( $this, 'filter_product_filter_data' ), 10, 4 );
}
@@ -43,6 +49,8 @@ class ProductFilterTaxonomyTest extends WC_Unit_Test_Case {
wp_delete_term( $term_id, 'product_cat' );
}
+ wc_get_container()->get( TaxonomyHierarchyData::class )->clear_cache( 'product_cat' );
+
parent::tearDown();
}
diff --git a/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/VariationSelectorAttribute.php b/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/VariationSelectorAttribute.php
index d540e79d158..e1637b4e3e5 100644
--- a/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/VariationSelectorAttribute.php
+++ b/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/VariationSelectorAttribute.php
@@ -6,8 +6,7 @@ namespace Automattic\WooCommerce\Tests\Blocks\BlockTypes;
use Automattic\WooCommerce\Enums\ProductStockStatus;
use Automattic\WooCommerce\Tests\Blocks\Helpers\FixtureData;
-use Automattic\WooCommerce\Tests\Blocks\Mocks\AddToCartWithOptionsVariationSelectorAttributeMock;
-use Automattic\WooCommerce\Tests\Blocks\Mocks\AddToCartWithOptionsVariationSelectorAttributeNameMock;
+use Automattic\WooCommerce\Tests\Blocks\Mocks\AddToCartWithOptionsMocks;
use WC_Unit_Test_Case;
/**
@@ -15,25 +14,16 @@ use WC_Unit_Test_Case;
*/
class VariationSelectorAttribute extends WC_Unit_Test_Case {
- /**
- * Tracks whether blocks have been registered.
- *
- * @var bool
- */
- protected static $are_blocks_registered = false;
-
/**
* Register blocks required for do_blocks tests.
*/
public function setUp(): void {
parent::setUp();
- if ( ! self::$are_blocks_registered && ! \WP_Block_Type_Registry::get_instance()->is_registered( 'woocommerce/add-to-cart-with-options-variation-selector-attribute' ) ) {
- new AddToCartWithOptionsVariationSelectorAttributeMock();
- new AddToCartWithOptionsVariationSelectorAttributeNameMock();
- }
-
- self::$are_blocks_registered = true;
+ AddToCartWithOptionsMocks::register(
+ 'add-to-cart-with-options-variation-selector-attribute',
+ 'add-to-cart-with-options-variation-selector-attribute-name'
+ );
}
/**
diff --git a/plugins/woocommerce/tests/php/src/Blocks/BlockTypesController.php b/plugins/woocommerce/tests/php/src/Blocks/BlockTypesController.php
index ec728729d3b..b54d3077213 100644
--- a/plugins/woocommerce/tests/php/src/Blocks/BlockTypesController.php
+++ b/plugins/woocommerce/tests/php/src/Blocks/BlockTypesController.php
@@ -36,6 +36,23 @@ class BlockTypesController extends WC_Unit_Test_Case {
);
}
+ /**
+ * Remove the block types test_block_should_have_data_attributes() registers, since the
+ * block type registry outlives a test.
+ */
+ public function tearDown(): void {
+ try {
+ $registry = \WP_Block_Type_Registry::get_instance();
+ foreach ( array( 'unrelated-namespace/unrelated-block-name', 'namespace/allowed-block-name', 'allowed-namespace/block-name', 'child-of-woo/block-name' ) as $block_name ) {
+ if ( $registry->is_registered( $block_name ) ) {
+ $registry->unregister( $block_name );
+ }
+ }
+ } finally {
+ parent::tearDown();
+ }
+ }
+
/**
* @testdox Should identify blocks that should have data attributes.
*/
diff --git a/plugins/woocommerce/tests/php/src/Blocks/Mocks/AddToCartWithOptionsMocks.php b/plugins/woocommerce/tests/php/src/Blocks/Mocks/AddToCartWithOptionsMocks.php
new file mode 100644
index 00000000000..84db3c741ff
--- /dev/null
+++ b/plugins/woocommerce/tests/php/src/Blocks/Mocks/AddToCartWithOptionsMocks.php
@@ -0,0 +1,46 @@
+<?php
+declare( strict_types = 1 );
+
+namespace Automattic\WooCommerce\Tests\Blocks\Mocks;
+
+/**
+ * Registers the Add to Cart with Options mock blocks, which `init` skips under a classic theme.
+ */
+class AddToCartWithOptionsMocks {
+
+ /**
+ * Mock class for each block name, without the `woocommerce/` namespace.
+ */
+ private const MOCKS = array(
+ 'add-to-cart-with-options' => AddToCartWithOptionsMock::class,
+ 'add-to-cart-with-options-quantity-selector' => AddToCartWithOptionsQuantitySelectorMock::class,
+ 'add-to-cart-with-options-grouped-product-selector' => AddToCartWithOptionsGroupedProductSelectorMock::class,
+ 'add-to-cart-with-options-grouped-product-item' => AddToCartWithOptionsGroupedProductItemMock::class,
+ 'add-to-cart-with-options-grouped-product-item-selector' => AddToCartWithOptionsGroupedProductItemSelectorMock::class,
+ 'add-to-cart-with-options-variation-selector' => AddToCartWithOptionsVariationSelectorMock::class,
+ 'add-to-cart-with-options-variation-selector-attribute' => AddToCartWithOptionsVariationSelectorAttributeMock::class,
+ 'add-to-cart-with-options-variation-selector-attribute-name' => AddToCartWithOptionsVariationSelectorAttributeNameMock::class,
+ );
+
+ /**
+ * Register the given mock blocks, skipping any already registered, since the registry outlives a test.
+ *
+ * @param string ...$block_names Block names without the `woocommerce/` namespace.
+ */
+ public static function register( string ...$block_names ): void {
+ $registry = \WP_Block_Type_Registry::get_instance();
+ foreach ( $block_names as $block_name ) {
+ if ( ! $registry->is_registered( 'woocommerce/' . $block_name ) ) {
+ $mock_class = self::MOCKS[ $block_name ];
+ new $mock_class();
+ }
+ }
+ }
+
+ /**
+ * Register every mock block that is not registered yet.
+ */
+ public static function register_all(): void {
+ self::register( ...array_keys( self::MOCKS ) );
+ }
+}
diff --git a/plugins/woocommerce/tests/php/src/Blocks/SharedStores/ProductsStore.php b/plugins/woocommerce/tests/php/src/Blocks/SharedStores/ProductsStore.php
index 7d2d2e9064f..6bb3d0ecf36 100644
--- a/plugins/woocommerce/tests/php/src/Blocks/SharedStores/ProductsStore.php
+++ b/plugins/woocommerce/tests/php/src/Blocks/SharedStores/ProductsStore.php
@@ -35,6 +35,14 @@ class ProductsStore extends \WC_Unit_Test_Case {
*/
protected $original_hydration_registry_entry = null;
+ /**
+ * Start from empty ProductsStore statics, since block renders in other test classes fill them.
+ */
+ public function setUp(): void {
+ parent::setUp();
+ $this->reset_products_store_static_state();
+ }
+
/**
* Reset static state on the ProductsStore and the global
* WP_Interactivity_API instance between tests so state does not bleed.
diff --git a/plugins/woocommerce/tests/php/src/Blocks/Shipping/ShippingControllerTest.php b/plugins/woocommerce/tests/php/src/Blocks/Shipping/ShippingControllerTest.php
index 82f34cfad4b..294fade93be 100644
--- a/plugins/woocommerce/tests/php/src/Blocks/Shipping/ShippingControllerTest.php
+++ b/plugins/woocommerce/tests/php/src/Blocks/Shipping/ShippingControllerTest.php
@@ -18,21 +18,6 @@ class ShippingControllerTest extends \WC_Unit_Test_Case {
*/
private ShippingController $shipping_controller;
- /**
- * The old checkout page ID.
- *
- * @var int $original_checkout_page_id
- */
- private $original_checkout_page_id;
-
- /**
- * The new checkout page ID.
- *
- * @var int $block_checkout_page_id
- */
- private $block_checkout_page_id;
-
-
/**
* Mock logger instance.
*
@@ -47,6 +32,13 @@ class ShippingControllerTest extends \WC_Unit_Test_Case {
*/
private $backup_wc;
+ /**
+ * Customer shipping address before the test, restored at teardown.
+ *
+ * @var array<string, string>
+ */
+ private $previous_shipping_address = array();
+
/**
* Initialize the registry instance.
*
@@ -66,8 +58,7 @@ class ShippingControllerTest extends \WC_Unit_Test_Case {
$this->backup_wc = WC();
// Local pickup only works with the checkout block.
- $this->original_checkout_page_id = get_option( 'woocommerce_checkout_page_id' );
- $this->block_checkout_page_id = $this->factory->post->create(
+ $block_checkout_page_id = $this->factory->post->create(
array(
'post_type' => 'page',
'post_title' => 'Checkout',
@@ -75,12 +66,17 @@ class ShippingControllerTest extends \WC_Unit_Test_Case {
'post_status' => 'publish',
)
);
- update_option( 'woocommerce_checkout_page_id', $this->block_checkout_page_id );
+ update_option( 'woocommerce_checkout_page_id', $block_checkout_page_id );
+ // An earlier test class may have loaded the shipping methods while the checkout was a
+ // shortcode page, which skips local pickup. Unload them so the next read registers it.
+ WC()->shipping()->unregister_shipping_methods();
$this->shipping_controller = new ShippingController(
Package::container()->get( Api::class ),
Package::container()->get( AssetDataRegistry::class )
);
+ // WC()->customer outlives the test, so keep its location to restore at teardown.
+ $this->previous_shipping_address = WC()->customer->get_shipping( 'edit' );
WC()->customer->set_shipping_postcode( '' );
WC()->customer->set_shipping_city( '' );
WC()->customer->set_shipping_state( '' );
@@ -95,11 +91,18 @@ class ShippingControllerTest extends \WC_Unit_Test_Case {
public function tearDown(): void {
global $woocommerce;
- update_option( 'woocommerce_checkout_page_id', $this->original_checkout_page_id );
- wp_delete_post( $this->block_checkout_page_id );
- remove_filter( 'woocommerce_logging_class', array( $this, 'override_wc_logger' ) );
- $woocommerce = $this->backup_wc;
- parent::tearDown();
+ try {
+ $woocommerce = $this->backup_wc;
+ foreach ( $this->previous_shipping_address as $key => $value ) {
+ WC()->customer->{"set_shipping_{$key}"}( $value );
+ }
+ // A test may null WC()->shipping, which leaves a dynamic property on the WC singleton,
+ // and register a lone pickup method on the shared WC_Shipping. Undo both.
+ unset( WC()->shipping );
+ WC()->shipping()->unregister_shipping_methods();
+ } finally {
+ parent::tearDown();
+ }
}
/**
diff --git a/plugins/woocommerce/tests/php/src/Blocks/StoreApi/RateLimitsTests.php b/plugins/woocommerce/tests/php/src/Blocks/StoreApi/RateLimitsTests.php
index d901334bfbf..84ddc78887a 100644
--- a/plugins/woocommerce/tests/php/src/Blocks/StoreApi/RateLimitsTests.php
+++ b/plugins/woocommerce/tests/php/src/Blocks/StoreApi/RateLimitsTests.php
@@ -11,16 +11,15 @@ use ReflectionClass;
use ReflectionException;
use Spy_REST_Server;
use WP_REST_Server;
-use WP_Test_REST_TestCase;
/**
* ControllerTests
*/
-class RateLimitsTests extends WP_Test_REST_TestCase {
+class RateLimitsTests extends \WC_Unit_Test_Case {
/**
* Setup Rest API server.
*/
- protected function setUp(): void {
+ public function setUp(): void {
parent::setUp();
/** @var WP_REST_Server $wp_rest_server */
diff --git a/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Schemas/OrderItemSchemaTest.php b/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Schemas/OrderItemSchemaTest.php
index 7e39e47d4e5..7db478eca8e 100644
--- a/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Schemas/OrderItemSchemaTest.php
+++ b/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Schemas/OrderItemSchemaTest.php
@@ -37,6 +37,20 @@ class OrderItemSchemaTest extends \WC_Unit_Test_Case {
*/
private $schema_controller;
+ /**
+ * Whether the pa_size taxonomy was registered when the test started.
+ *
+ * @var bool
+ */
+ private $size_taxonomy_existed_at_setup;
+
+ /**
+ * The $wc_product_attributes global when the test started.
+ *
+ * @var array<string, object>
+ */
+ private $product_attributes_at_setup;
+
/**
* Set up before test.
*
@@ -45,6 +59,10 @@ class OrderItemSchemaTest extends \WC_Unit_Test_Case {
public function setUp(): void {
parent::setUp();
+ // create_attribute() registers a taxonomy and rebuilds this global, and the rollback undoes neither.
+ $this->size_taxonomy_existed_at_setup = taxonomy_exists( 'pa_size' );
+ $this->product_attributes_at_setup = $GLOBALS['wc_product_attributes'] ?? array();
+
// Set up formatters and extend schema.
$formatters = new Formatters();
$formatters->register( 'money', MoneyFormatter::class );
@@ -63,7 +81,14 @@ class OrderItemSchemaTest extends \WC_Unit_Test_Case {
$this->schema_controller = null;
$this->mock_extend = null;
- parent::tearDown();
+ try {
+ if ( ! $this->size_taxonomy_existed_at_setup && taxonomy_exists( 'pa_size' ) ) {
+ unregister_taxonomy( 'pa_size' );
+ }
+ $GLOBALS['wc_product_attributes'] = $this->product_attributes_at_setup; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- Restores the value from setUp().
+ } finally {
+ parent::tearDown();
+ }
}
/**
@@ -245,6 +270,9 @@ class OrderItemSchemaTest extends \WC_Unit_Test_Case {
* Test that variations with "Any" attributes return user-selected values.
*/
public function test_get_item_response_any_variation_returns_user_selected_values(): void {
+ // Register the pa_size taxonomy so the attribute label resolves the same way in any test order.
+ \WC_Helper_Product::create_attribute( 'size', array() );
+
$variable_product = new \WC_Product_Variable();
$variable_product->set_name( 'Test Variable Product' );
@@ -305,6 +333,9 @@ class OrderItemSchemaTest extends \WC_Unit_Test_Case {
* Test variations with mixed global and custom "Any" attributes.
*/
public function test_get_item_response_any_variation_with_mixed_attributes(): void {
+ // Register the pa_size taxonomy so the attribute label resolves the same way in any test order.
+ \WC_Helper_Product::create_attribute( 'size', array() );
+
$variable_product = new \WC_Product_Variable();
$variable_product->set_name( 'Test Variable Product with Mixed Attributes' );
diff --git a/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Utilities/OrderControllerTests.php b/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Utilities/OrderControllerTests.php
index 2d50ec9cbec..2a6dbb18d9e 100644
--- a/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Utilities/OrderControllerTests.php
+++ b/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Utilities/OrderControllerTests.php
@@ -21,6 +21,13 @@ class OrderControllerTests extends \WC_Unit_Test_Case {
*/
private $sut;
+ /**
+ * Customer shipping address before the test, restored at teardown.
+ *
+ * @var array<string, string>
+ */
+ private $previous_shipping_address = array();
+
/**
* Set up before test.
*
@@ -35,6 +42,12 @@ class OrderControllerTests extends \WC_Unit_Test_Case {
// The per-test database rollback restores the option.
update_option( 'woocommerce_checkout_phone_field', 'optional' );
+ // The invalid-address tests need a shipping rate, and the flat rate only offers one for a
+ // country the store ships to. The cart ships to WC()->customer, which outlives a test, so
+ // give it a US/CA location here rather than depend on the address it arrives with.
+ $this->previous_shipping_address = WC()->customer->get_shipping( 'edit' );
+ WC()->customer->set_shipping_location( 'US', 'CA' );
+
$this->sut = new class() extends OrderController {
/**
* Check all required address fields are set and return errors if not. Parent is protected.
@@ -49,6 +62,19 @@ class OrderControllerTests extends \WC_Unit_Test_Case {
};
}
+ /**
+ * Put back the customer shipping location that setUp() replaced.
+ */
+ public function tearDown(): void {
+ try {
+ foreach ( $this->previous_shipping_address as $key => $value ) {
+ WC()->customer->{"set_shipping_{$key}"}( $value );
+ }
+ } finally {
+ parent::tearDown();
+ }
+ }
+
/**
* test_validate_existing_order_before_payment_valid_data.
*/
diff --git a/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/PaymentsProviders/WooPayments/WooPaymentsServiceTest.php b/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/PaymentsProviders/WooPayments/WooPaymentsServiceTest.php
index aa173124af1..16bfb82a5bf 100644
--- a/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/PaymentsProviders/WooPayments/WooPaymentsServiceTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/PaymentsProviders/WooPayments/WooPaymentsServiceTest.php
@@ -206,6 +206,17 @@ class WooPaymentsServiceTest extends WC_Unit_Test_Case {
$this->sut->init( $this->mock_providers, $this->mockable_proxy );
}
+ /**
+ * Clear the version constant setUp() sets, which would otherwise outlive the class.
+ */
+ public function tearDown(): void {
+ try {
+ Constants::clear_single_constant( 'WCPAY_VERSION_NUMBER' );
+ } finally {
+ parent::tearDown();
+ }
+ }
+
/**
* Test get onboarding details when the extension is NOT active.
*/
diff --git a/plugins/woocommerce/tests/php/src/Internal/LegacyAssets/LegacySelect2UsageTrackerTest.php b/plugins/woocommerce/tests/php/src/Internal/LegacyAssets/LegacySelect2UsageTrackerTest.php
index 882776c9f18..eac41c7e731 100644
--- a/plugins/woocommerce/tests/php/src/Internal/LegacyAssets/LegacySelect2UsageTrackerTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/LegacyAssets/LegacySelect2UsageTrackerTest.php
@@ -406,7 +406,8 @@ class LegacySelect2UsageTrackerTest extends WC_Unit_Test_Case {
private function reset_scripts(): void {
$wp_scripts = wp_scripts();
- $wp_scripts->queue = array();
+ // dequeue() also clears the cached dependency list that wp_script_is( ..., 'enqueued' ) reads.
+ $wp_scripts->dequeue( $wp_scripts->queue );
$wp_scripts->to_do = array();
$wp_scripts->done = array();
$wp_scripts->groups = array();
@@ -448,14 +449,19 @@ class LegacySelect2UsageTrackerTest extends WC_Unit_Test_Case {
/**
* Get the frontend page type expected for the current PHPUnit process.
*
- * Some tests render cart flows that define WOOCOMMERCE_CART. Since PHP
- * constants cannot be undefined, this test must accept the inherited cart
- * page type when it runs later in the same process.
+ * Some tests render cart or checkout flows that define WOOCOMMERCE_CART or
+ * WOOCOMMERCE_CHECKOUT. Since PHP constants cannot be undefined, this test must
+ * accept the inherited page type when it runs later in the same process. The
+ * checks follow the tracker's own order: cart before checkout.
*
* @return string
*/
private function get_expected_frontend_page_type(): string {
- return Constants::is_defined( 'WOOCOMMERCE_CART' ) ? 'cart' : 'other';
+ if ( Constants::is_defined( 'WOOCOMMERCE_CART' ) ) {
+ return 'cart';
+ }
+
+ return Constants::is_defined( 'WOOCOMMERCE_CHECKOUT' ) ? 'checkout' : 'other';
}
/**
diff --git a/plugins/woocommerce/tests/php/src/Internal/StockNotifications/Frontend/ProductPageIntegrationTest.php b/plugins/woocommerce/tests/php/src/Internal/StockNotifications/Frontend/ProductPageIntegrationTest.php
index e4fa22ce660..6983aa79eba 100644
--- a/plugins/woocommerce/tests/php/src/Internal/StockNotifications/Frontend/ProductPageIntegrationTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/StockNotifications/Frontend/ProductPageIntegrationTest.php
@@ -6,11 +6,7 @@ namespace Automattic\WooCommerce\Tests\Internal\StockNotifications\Frontend;
use Automattic\WooCommerce\Enums\ProductStockStatus;
use Automattic\WooCommerce\Internal\StockNotifications\Frontend\ProductPageIntegration;
use Automattic\WooCommerce\Tests\Blocks\Helpers\FixtureData;
-use Automattic\WooCommerce\Tests\Blocks\Mocks\AddToCartWithOptionsMock;
-use Automattic\WooCommerce\Tests\Blocks\Mocks\AddToCartWithOptionsQuantitySelectorMock;
-use Automattic\WooCommerce\Tests\Blocks\Mocks\AddToCartWithOptionsVariationSelectorMock;
-use Automattic\WooCommerce\Tests\Blocks\Mocks\AddToCartWithOptionsVariationSelectorAttributeMock;
-use Automattic\WooCommerce\Tests\Blocks\Mocks\AddToCartWithOptionsVariationSelectorAttributeNameMock;
+use Automattic\WooCommerce\Tests\Blocks\Mocks\AddToCartWithOptionsMocks;
use Automattic\WooCommerce\Tests\Internal\StockNotifications\StockNotificationsFeatureTrait;
use WC_Unit_Test_Case;
@@ -28,21 +24,29 @@ class ProductPageIntegrationTest extends WC_Unit_Test_Case {
*/
private $sut;
+ /**
+ * REQUEST_URI before the test, restored at teardown.
+ *
+ * @var string|null
+ */
+ private $request_uri_at_setup;
+
/**
* Set up test fixtures.
*/
public function setUp(): void {
parent::setUp();
- // The blocks are not registered on `init` because `init` runs with a classic theme.
- // Other test classes may have registered them already.
- if ( ! \WP_Block_Type_Registry::get_instance()->is_registered( 'woocommerce/add-to-cart-with-options' ) ) {
- new AddToCartWithOptionsMock();
- new AddToCartWithOptionsQuantitySelectorMock();
- new AddToCartWithOptionsVariationSelectorMock();
- new AddToCartWithOptionsVariationSelectorAttributeMock();
- new AddToCartWithOptionsVariationSelectorAttributeNameMock();
- }
+ // go_to() sets REQUEST_URI and no base test case puts it back.
+ $this->request_uri_at_setup = $_SERVER['REQUEST_URI'] ?? null; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput
+
+ AddToCartWithOptionsMocks::register(
+ 'add-to-cart-with-options',
+ 'add-to-cart-with-options-quantity-selector',
+ 'add-to-cart-with-options-variation-selector',
+ 'add-to-cart-with-options-variation-selector-attribute',
+ 'add-to-cart-with-options-variation-selector-attribute-name'
+ );
update_option( 'woocommerce_customer_stock_notifications_allow_signups', 'yes' );
@@ -59,6 +63,11 @@ class ProductPageIntegrationTest extends WC_Unit_Test_Case {
*/
public function tearDown(): void {
try {
+ if ( null === $this->request_uri_at_setup ) {
+ unset( $_SERVER['REQUEST_URI'] );
+ } else {
+ $_SERVER['REQUEST_URI'] = $this->request_uri_at_setup;
+ }
$this->restore_stock_notifications_feature_option();
} finally {
parent::tearDown();