Skip to content

Test the Application

Configuration-dependent tests need different setup depending on the boundary under test. Construct a fresh container when testing one provider. Start a new PHP process when the complete WordPress application must read different environment values during bootstrap.

Focused provider tests should pass configuration values directly to a new ArrayConfiguration. Add a helper to the project’s base test case so every call creates a container with an independent configuration snapshot.

Create tests/TestCase.php:

<?php declare(strict_types=1);

namespace Plugin\Tests;

use lucatume\WPBrowser\TestCase\WPTestCase;
use StellarWP\Foundation\Container\Configuration\ArrayConfiguration;
use StellarWP\Foundation\Container\ContainerFactory;
use StellarWP\Foundation\Container\Contracts\Container;

use function Plugin\plugin;

abstract class TestCase extends WPTestCase {

	protected Container $container;

	protected function setUp(): void {
		parent::setUp();

		$this->container = plugin()->container();
	}

	/**
	 * Create a new container using the supplied configuration values.
	 *
	 * @param array<string, mixed> $configuration
	 */
	protected function create_container( array $configuration = [] ): Container {
		return ( new ContainerFactory() )->create(
			new ArrayConfiguration( $configuration )
		);
	}
}

Use the helper to create a fresh container for each scenario so registered bindings and resolved singletons cannot retain values from another test:

<?php declare(strict_types=1);

namespace Plugin\Tests\WPUnit\Catalog;

use Plugin\Catalog\Catalog_Provider;
use Plugin\Catalog\Catalog_Synchronizer;
use Plugin\Tests\TestCase;

final class Catalog_Provider_Test extends TestCase {

	public function test_it_disables_catalog_synchronization(): void {
		$container = $this->create_container( [
			'catalog' => [
				'sync_enabled' => false,
			],
		] );
		$container->register( Catalog_Provider::class );

		$synchronizer = $container->get( Catalog_Synchronizer::class );

		$this->assertFalse( $synchronizer->is_enabled() );
	}
}

The shared test case exposes the normally booted application container through $this->container. Use create_container() when a provider scenario needs different configuration without replacing that application container or changing process-level environment values. Because the base class loads WordPress, providers can still register hooks and use WordPress APIs.

Tests that exercise the complete application need a new PHP process. wp-browser’s WPLoader loads WordPress and active plugins before PHPUnit runs a test class’s setup hooks, so changing $_ENV in an ordinary setUp() method does not reconfigure an App singleton that already exists.

Enable wp-browser’s isolation extension in the root codeception.dist.yml. The extension starts another Codeception process for isolated tests so WPLoader can boot WordPress and the application from a clean PHP process:

extensions:
  enabled:
    - lucatume\WPBrowser\Extension\IsolationSupport

Set the environment in the standard setUpBeforeClass() hook and mark the class with RunTestsInSeparateProcesses. Although WPLoader has already booted the application in the outer test process, wp-browser launches the isolated Codeception process when the test method begins. The child inherits the environment before its own WPLoader initializes, so it constructs a new application with the configured value.

Synchronize the process environment with $_ENV and $_SERVER so Symfony Process passes a newly introduced variable to the child:

<?php declare(strict_types=1);

namespace Plugin\Tests\WPUnit\Catalog;

use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
use Plugin\Catalog\Catalog_Synchronizer;
use Plugin\Tests\TestCase;

#[RunTestsInSeparateProcesses]
final class Catalog_Application_Test extends TestCase {

	private const string ENV_SYNC_ENABLED = 'CATALOG_SYNC_ENABLED';

	private static string|false $original_sync_enabled;

	public static function setUpBeforeClass(): void {
		self::$original_sync_enabled = getenv( self::ENV_SYNC_ENABLED );
		self::set_sync_enabled_environment( 'true' );

		parent::setUpBeforeClass();
	}

	public static function tearDownAfterClass(): void {
		if ( self::$original_sync_enabled === false ) {
			putenv( self::ENV_SYNC_ENABLED );
			unset( $_ENV[ self::ENV_SYNC_ENABLED ], $_SERVER[ self::ENV_SYNC_ENABLED ] );
		} else {
			self::set_sync_enabled_environment( self::$original_sync_enabled );
		}

		parent::tearDownAfterClass();
	}

	public function test_it_boots_with_catalog_synchronization_enabled(): void {
		$synchronizer = $this->container->get( Catalog_Synchronizer::class );

		$this->assertTrue( $synchronizer->is_enabled() );
	}

	private static function set_sync_enabled_environment( string $value ): void {
		putenv( self::ENV_SYNC_ENABLED . '=' . $value );
		$_ENV[ self::ENV_SYNC_ENABLED ]    = $value;
		$_SERVER[ self::ENV_SYNC_ENABLED ] = $value;
	}
}

Use one isolated test class for each application configuration. The underscored _setUpBeforeClass() hook remains available as a Codeception compatibility API, but new tests should use PHPUnit’s standard setUpBeforeClass() method.