|
| 1 | +<?php |
| 2 | + |
| 3 | +declare(strict_types=1); |
| 4 | + |
| 5 | +namespace MintyPHP\Mocking; |
| 6 | + |
| 7 | +class StaticMethodMock |
| 8 | +{ |
| 9 | + /** @var ?callable */ |
| 10 | + private static $autoloader = null; |
| 11 | + /** @var array<string,StaticMethodMock> */ |
| 12 | + public static array $mocks = []; |
| 13 | + /** @var string */ |
| 14 | + private string $className; |
| 15 | + /** @var array<int,array{method:string,arguments:array,returnsVoid:bool,returns:mixed,exception:?Throwable}> */ |
| 16 | + private array $expectations = []; |
| 17 | + |
| 18 | + // Register a static mock for the given class name. |
| 19 | + public function __construct(string $className) { |
| 20 | + $this->className = $className; |
| 21 | + self::$mocks[$className] = $this; |
| 22 | + if (self::$autoloader === null) { |
| 23 | + self::$autoloader = function (string $class) { |
| 24 | + if ($class === $this->className) { |
| 25 | + $namespace = substr($this->className, 0, strrpos($this->className, '\\')); |
| 26 | + $shortClassName = substr($this->className, strrpos($this->className, '\\') + 1); |
| 27 | + eval('namespace ' . $namespace . ' { class ' . $shortClassName . ' { public static function __callStatic($name, $arguments) { return \MintyPHP\Tests\StaticMethodMock::handleStaticCall(\'' . $this->className . '\', $name, $arguments); } } }'); |
| 28 | + return true; |
| 29 | + } |
| 30 | + return false; |
| 31 | + }; |
| 32 | + spl_autoload_register(self::$autoloader, true, true); |
| 33 | + } |
| 34 | + } |
| 35 | + |
| 36 | + /** Expect a with specific body (exact match). */ |
| 37 | + public function expect(string $method, array $arguments, bool $returnsVoid = true, mixed $returns = null, ?\Throwable $exception = null) |
| 38 | + { |
| 39 | + $this->expectations[] = [ |
| 40 | + 'method' => strtoupper($method), |
| 41 | + 'arguments' => $arguments, |
| 42 | + 'returnsVoid' => $returnsVoid, |
| 43 | + 'returns' => $returns, |
| 44 | + 'exception' => $exception, |
| 45 | + ]; |
| 46 | + } |
| 47 | + |
| 48 | + public static function handleStaticCall(string $className, string $method, array $arguments) |
| 49 | + { |
| 50 | + if (!isset(self::$mocks[$className])) { |
| 51 | + throw new \Exception(sprintf('StaticMethodMock no mock registered for class: %s', $className)); |
| 52 | + } |
| 53 | + $mock = self::$mocks[$className]; |
| 54 | + if (empty($mock->expectations)) { |
| 55 | + throw new \Exception(sprintf('StaticMethodMock unexpected call: %s::%s', $className, $method)); |
| 56 | + } |
| 57 | + $expected = array_shift($mock->expectations); |
| 58 | + // Basic matching |
| 59 | + if ($expected['method'] != strtoupper($method)) { |
| 60 | + throw new \Exception(sprintf('StaticMethodMock method mismatch: expected %s got %s for %s::%s', $expected['method'], strtoupper($method), $className, $method)); |
| 61 | + } |
| 62 | + if ($expected['arguments'] != $arguments) { |
| 63 | + throw new \Exception(sprintf('StaticMethodMock arguments mismatch for %s::%s: expected %s got %s', $className, $method, json_encode($expected['arguments']), json_encode($arguments))); |
| 64 | + } |
| 65 | + if ($expected['exception'] !== null) { |
| 66 | + throw $expected['exception']; |
| 67 | + } |
| 68 | + if ($expected['returnsVoid']) { |
| 69 | + return; |
| 70 | + } |
| 71 | + return $expected['returns']; |
| 72 | + } |
| 73 | +} |
0 commit comments