diff --git a/docs/usage/using-wp-mock.md b/docs/usage/using-wp-mock.md index 304a031..29fed4a 100644 --- a/docs/usage/using-wp-mock.md +++ b/docs/usage/using-wp-mock.md @@ -74,6 +74,16 @@ For example, the invocation below will set the expectation that the `get_permali WP_Mock::userFunction('get_permalink')->once()->with(42)->andReturn('https://example.com/foo'); ``` +To mock functions that are called using named parameters, e.g. `get_permalink(post: 42)`, you must pass the arguments as an associative array: + +```php +WP_Mock::userFunction('get_permalink', [ + 'args' => [ + 'post' => '42', + ], +])->once()->andReturn('https://example.com/foo'); +``` + ## Using expectations in arguments You can also pass an associative array of arguments to the second parameter of `WP_Mock::userFunction()` to set expectations about the function's arguments, the number of times it should be called, and what it should return. diff --git a/php/WP_Mock/Functions.php b/php/WP_Mock/Functions.php index ddf5cf6..c69c076 100644 --- a/php/WP_Mock/Functions.php +++ b/php/WP_Mock/Functions.php @@ -97,7 +97,8 @@ public function flush(): void */ public function register(string $function, array $args = []) { - $this->generateFunction($function); + $functionArgs = isset($args['args']) && is_array($args['args']) ? $args['args'] : []; + $this->generateFunction($function, $functionArgs); if (empty($this->mockedFunctions[$function])) { /** @phpstan-ignore-next-line */ @@ -138,7 +139,10 @@ protected function setUpMock($mock, string $functionName, array $args = []) // set the expected arguments the function should be called with if (isset($args['args'])) { - $this->setExpectedArgs($expectation, $args['args']); + $this->setExpectedArgs( + $expectation, + is_array($args['args']) ? array_values($args['args']): $args['args'] + ); } // set the expected return value based on a passed argument or return values for each call in order @@ -272,25 +276,27 @@ protected function setExpectedReturn(&$expectation, $return) * The declared function is namespace-aware. * * @param string $functionName function name + * @param array $functionArgs function arguments * @return void * @throws InvalidArgumentException */ - protected function generateFunction(string $functionName): void + protected function generateFunction(string $functionName, array $functionArgs = []): void { $functionName = $this->sanitizeFunctionName($functionName); $this->validateFunctionName($functionName); - $this->createFunction($functionName) or $this->replaceFunction($functionName); + $this->createFunction($functionName, $functionArgs) or $this->replaceFunction($functionName); } /** * Creates a function using eval. * * @param string $functionName function name + * @param array $functionArgs function arguments, required only when calling mocked functions using named parameters * @return bool true if this function created the mock, false otherwise */ - protected function createFunction(string $functionName): bool + protected function createFunction(string $functionName, array $functionArgs = []): bool { if (in_array($functionName, self::$userMockedFunctions, true)) { return true; @@ -304,9 +310,19 @@ protected function createFunction(string $functionName): bool $name = array_pop($parts); $namespace = empty($parts) ? '' : 'namespace '.implode('\\', $parts).';'.PHP_EOL; + $functionNamedParameters = ''; + + $has_named_parameters = array_reduce( array_keys($functionArgs), function ($carry, $arg) { + return $carry && !is_int($arg); + }, true); + + if($has_named_parameters){ + $functionNamedParameters = implode(', ', array_map(fn($name) => '$' . $name, array_keys($functionArgs))); + } + $declaration = <<assertConditionsMet(); } + + /** + * Fix "Error : Unknown named parameter $transient" when passing named parameters in the argument list. + * + * @covers \WP_Mock::userFunction() + * @see WP_Mock\Functions::createFunction() + * + * @throws Exception + */ + public function testCanMockNamedParameters(): void { + + if(!version_compare(PHP_VERSION, '8.0', '>=')) { + $this->markTestSkipped('PHP 8.0 required for named parameters.'); + } + + WP_Mock::userFunction('get_transient', [ + 'times' => 1, + 'args' => [ + 'transient' => 'my-transient-name', + ], + 'return' => 'the-mocked-transient-value', + ]); + + // Without this, tests fail on PHP 7.4. + // PHP Fatal error: Uncaught ParseError: syntax error, unexpected ':', expecting ')' in :362 + $hidePhp8CodeFromOlderVersions = <<<'PHP' + return get_transient(transient: 'my-transient-name'); + PHP; + + /** @phpstan-ignore-next-line function "exists" */ + $this->assertEquals('the-mocked-transient-value', eval($hidePhp8CodeFromOlderVersions)); + } }