Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 80 additions & 1 deletion php/WP_Mock.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,19 @@ class WP_Mock

protected static $__strict_mode = false;

/**
* A record if strict mode was set individually for this test.
*
* Uses an associative array containing both method and setting as test-method-string:is-enabled-bool.
*
* @used-by self::setStrictModeForTest()
* @used-by self::isStrictModeForTest()
* @see self::strictMode()
*
* @var array<string, bool>
*/
protected static $__strict_mode_for_individual_test = [];

/** @var DeprecatedMethodListener */
protected static $deprecatedMethodListener;

Expand All @@ -57,7 +70,7 @@ public static function usingPatchwork()
*/
public static function strictMode()
{
return (bool) self::$__strict_mode;
return self::isStrictModeForTest() ?? (bool) self::$__strict_mode;
}

/**
Expand All @@ -70,6 +83,72 @@ public static function activateStrictMode()
}
}

/**
* Sets strict mode on or off at runtime for an individual test.
*
* Records the config/preference for the individual test. Later this will be preferred over the default.
*
* @param bool $enabled
* @throws Exception when the test case name cannot be determined.
*/
public static function setStrictModeForTest(bool $enabled = true): void
{
$currentTestName = self::getCurrentlyRunningTestName();
if(is_null($currentTestName)){
throw new Exception('Failed to determine current test name');
}
self::$__strict_mode_for_individual_test = [$currentTestName => $enabled,];
}

/**
* Check was strict mode configured individually for this test case.
*
* @see self::setStrictModeForTest()
* @see self::$__strict_mode_for_individual_test
*
* @return ?bool `null` when not set, boolean preference when set.
*/
protected static function isStrictModeForTest(): ?bool {
if( empty( self::$__strict_mode_for_individual_test ) ) {
return null;
}

$currentTestName = self::getCurrentlyRunningTestName();

if(!is_null($currentTestName) && isset(self::$__strict_mode_for_individual_test[$currentTestName])) {
return self::$__strict_mode_for_individual_test[$currentTestName];
}

// Reset the array since it is only relevant for the current test case run.
self::$__strict_mode_for_individual_test = [];

return null;
}

/**
* Perform a backtrace to determine the currently running test.
*
* @return ?string of `class-string::method`
*/
protected static function getCurrentlyRunningTestName(): ?string {
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);

/** @var array{class?:string, function?:string} $trace */
foreach ($backtrace as $trace) {
if (isset($trace['class']) && isset($trace['function'])) {
// Check if this is a PHPUnit test class
if (is_subclass_of($trace['class'], \PHPUnit\Framework\TestCase::class)) {
// Test method names start with 'test' or have @test annotation
if (strpos($trace['function'], 'test') === 0) {
return $trace['class'] . '::' . $trace['function'];
}
}
}
}

return null;
}

/**
* Bootstraps WP_Mock.
*
Expand Down
71 changes: 71 additions & 0 deletions tests/Integration/WP_MockTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,18 @@ class WP_MockTest extends WP_MockTestCase
*/
protected function setUp(): void
{
/**
* Reset to default strict-mode after tests that manipulate this value.
*
* @see WP_Mock::$__strict_mode
*/
$property = new \ReflectionProperty( \WP_Mock::class, '__strict_mode' );
// "Method ReflectionProperty::setAccessible() is deprecated since 8.5, as it has no effect".
if(!version_compare(PHP_VERSION, '8.5', '>=')) {
$property->setAccessible( true );
}
$property->setValue( null, false );

if (! $this->isInIsolation()) {
WP_Mock::setUp();
}
Expand Down Expand Up @@ -335,4 +347,63 @@ public function testCanExpectHooksNotAdded() : void

$this->assertConditionsMet();
}

/**
* @covers \WP_Mock::setStrictModeForTest()
*
* @return void
* @throws Exception
*/
public function testCanDisableStrictModeForLegacyCode() : void
{
// Set default ala `WP_Mock::activateStrictMode()`.
$property = new \ReflectionProperty( WP_Mock::class, '__strict_mode' );
if(!version_compare(PHP_VERSION, '8.5', '>=')) {
$property->setAccessible( true );
}
$property->setValue( null, true );

// Temporarily disable strict mode to test legacy code
WP_Mock::setStrictModeForTest(false);

// This would normally fail in strict mode, but should pass now
add_action('legacy_action', 'legacy_callback');

$this->assertTrue(true);
}

/**
* @covers \WP_Mock::setStrictModeForTest()
*
* @runInSeparateProcess
* @preserveGlobalState disabled
*
* @return void
* @throws Exception|ExpectationFailedException
*/
public function testCanEnableStrictModeForSpecificTest() : void
{
/**
* Set default ala `WP_Mock::activateStrictMode()`, `false`.
* @see \WP_Mock::$__strict_mode
*/
$property = new \ReflectionProperty( \WP_Mock::class, '__strict_mode' );
if(!version_compare(PHP_VERSION, '8.5', '>=')) {
$property->setAccessible( true );
}
$property->setValue( null, false );

$this->assertFalse(WP_Mock::strictMode());

// Enable strict mode for this specific test
WP_Mock::setStrictModeForTest();
$this->assertTrue(WP_Mock::strictMode());

// This should throw an exception because we haven't set expectations
$this->expectException(ExpectationFailedException::class);
$this->expectExceptionMessage('No handler found for function unmocked_function');

// Call an unmocked function
WP_Mock\Functions\Handler::handleFunction('unmocked_function');
}
}
60 changes: 60 additions & 0 deletions tests/Unit/WP_MockTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,66 @@ public function testActivateStrictModeDoesNotWorkAfterBootstrap(): void
$this->assertFalse(WP_Mock::strictMode());
}

/**
* @covers \WP_Mock::setStrictModeForTest()
*
* @return void
* @throws \Exception
*/
public function testSetStrictModeForTestCanDisableStrictMode(): void
{
// Set default ala `WP_Mock::activateStrictMode()`.
$property = new \ReflectionProperty( WP_Mock::class, '__strict_mode' );
$property->setAccessible( true );
$property->setValue( null, true );


WP_Mock::setStrictModeForTest(false);

$this->assertFalse(WP_Mock::strictMode());
}

/**
* @covers \WP_Mock::setStrictModeForTest()
*
* @return void
* @throws \Exception
*/
public function testSetStrictModeForTestCanEnableStrictMode(): void
{
// Set default ala `WP_Mock::activateStrictMode()`, but `false`.
$property = new \ReflectionProperty( WP_Mock::class, '__strict_mode' );
$property->setAccessible( true );
$property->setValue( null, false );

WP_Mock::setStrictModeForTest();

$this->assertTrue(WP_Mock::strictMode());
}

/**
* @covers \WP_Mock::setStrictModeForTest()
*
* @return void
* @throws ExpectationFailedException|InvalidArgumentException
*/
public function testPreviousSetStrictModeForTestIsNotRelevant(): void
{
// Set default ala `WP_Mock::activateStrictMode()`.
$property = new \ReflectionProperty( WP_Mock::class, '__strict_mode' );
$property->setAccessible( true );
$property->setValue( null, true );

// Set individual test configuration for another tests.
$property = new \ReflectionProperty( WP_Mock::class, '__strict_mode_for_individual_test' );
$property->setAccessible( true );
$property->setValue( null, [
'WP_Mock\Tests\Unit\WP_MockTest::testSetStrictModeForTestCanDisableStrictMode' => false
]);

$this->assertTrue(WP_Mock::strictMode());
}

/**
* @covers \WP_Mock::userFunction()
*
Expand Down