assertTrue( method_exists(is_object($class) ? $class::class : $class, $method), "Method {$class}::{$method}() does not exist" ); } } protected function makePlaylist(): void { } protected function makeIni(?string $contents = null): string { $contents ??= <<<'EOD' [foo] name=foo name desc=foo description url=http://example.com/foo.m3u src=http://example.com/ [bar] name=bar name desc=bar description url=http://example.com/bar.m3u src=http://example.com/ EOD; return data_stream($contents, 'text/ini'); } /* |-------------------------------------------------------------------------- | Методы для заглушки объектов и методов других классов |-------------------------------------------------------------------------- */ /** * Создаёт и возвращает объект HTTP-запроса для тестирования методов контроллеров * * @param array $query The GET parameters * @param array $request The POST parameters * @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...) * @param array $cookies The COOKIE parameters * @param array $files The FILES parameters * @param array $server The SERVER parameters * @param resource|string|null $content The raw body data * * @return HttpRequest * * @see Request::__construct */ protected function makeHttpRequest( array $query = [], array $request = [], array $attributes = [], array $cookies = [], array $files = [], array $server = [], mixed $content = null ): HttpRequest { return new HttpRequest(func_get_args()); } /** * Возвращает объект ответа HTTP-клиента Laravel * * @param int $status * @param array $headers * @param $body * @param string $version * @param string|null $reason * * @return HttpClientResponse */ protected function makeHttpResponse( int $status = 200, array $headers = [], $body = null, string $version = '1.1', ?string $reason = null ): HttpClientResponse { return new HttpClientResponse(new GuzzleResponse(...func_get_args())); } /** * Вызывает любой метод указанного объекта с нужными аргументами и обходом его видимости * * @param class-string|object $objectOrClass * @param string $method * @param mixed ...$args * * @return mixed * * @throws ReflectionException * * @see https://stackoverflow.com/questions/249664 * @see \Psy\Sudo::callMethod() * @see \Psy\Sudo::callStatic() */ protected function callMethod(object|string $objectOrClass, string $method, mixed ...$args): mixed { $reflObject = is_string($objectOrClass) ? new ReflectionClass($objectOrClass) : new ReflectionObject($objectOrClass); $reflMethod = $reflObject->getMethod($method); return $reflMethod->invokeArgs(is_string($objectOrClass) ? null : $objectOrClass, $args); } /* |-------------------------------------------------------------------------- | Методы-хелперы для подготовки и отладки тестов |-------------------------------------------------------------------------- */ /** * Конвертирует многоуровневый массив в html-файл с таблицей для визуальной * отладки и сохраняет в `"storage/app/$filename"`. * * Использование: * 0. предполагается во время отладки теста * 1. вызвать метод, который возвращает массив, приводимый к массиву или читаемый как массив объект * 2. вызвать `$this->toTable($array, 'test')` * 3. открыть в браузере файл `storage/app/test.html` * * @param array|ArrayAccess $data * @param string $filename */ protected static function toTable(array|ArrayAccess $data, string $filename): void { $headers = $result = $html = []; foreach ($data as $row) { $result[] = $row = Arr::dot($row); empty($headers) && $headers = array_keys($row); } $html[] = ''; foreach ($headers as $header) { $html[] = ""; } $html[] = ''; foreach ($result as $row) { $html[] = ''; foreach ($row as $value) { $value instanceof BackedEnum && $value = $value->value; $value = str_replace("'", '', var_export($value, true)); // строки без кавычек $html[] = ""; } $html[] = ''; } $html[] = '
{$header}
{$value}
'; Storage::put("{$filename}.html", implode('', $html)); } /* |-------------------------------------------------------------------------- | Методы проверки значений |-------------------------------------------------------------------------- */ /** * Проверяет идентичность двух классов * * @param object|string $class1 * @param object|string $class2 * * @return bool */ protected function checkIsSameClass(object|string $class1, object|string $class2): bool { return (is_object($class1) ? $class1::class : $class1) === (is_object($class2) ? $class2::class : $class2); } /** * Проверяет наследование других классов указанным * * @param string[] $parents Массив имён потенциальных классов-родителей * @param object|string $class Объект или имя класса для проверки * * @see https://www.php.net/manual/en/function.class-parents.php */ protected function checkExtendsClasses(array $parents, object|string $class): bool { return !empty(array_intersect($parents, is_object($class) ? class_parents($class) : [$class])); } /** * Проверяет реализацию интерфейсов указанным классом * * @param string[] $interfaces Массив имён интерфейсов * @param object|string $class Объект или имя класса для проверки * * @see https://www.php.net/manual/en/function.class-parents.php */ protected function checkImplementsInterfaces(array $interfaces, object|string $class): bool { return !empty(array_intersect($interfaces, is_object($class) ? class_implements($class) : [$class])); } /* |-------------------------------------------------------------------------- | Методы проверки утверждений в тестах |-------------------------------------------------------------------------- */ /** * Утверждает, что в массиве имеются все указанные ключи * * @param array $keys Ключи для проверки в массиве * @param iterable $array Проверяемый массив, итератор или приводимый к массиву объект * * @return void */ protected function assertArrayHasKeys(array $keys, iterable $array): void { $array = iterator_to_array($array); foreach ($keys as $key) { $this->assertArrayHasKey($key, $array); } } /** * Утверждает, что в объекте имеются все указанные свойства * * @param array $props Свойства для проверки в объекте * @param object $object Проверяемый объект * * @return void */ protected function assertObjectHasProperties(array $props, object $object): void { foreach ($props as $prop) { $this->assertObjectHasProperty($prop, $object); } } /** * Утверждает, что в массиве отсутствуют все указанные ключи * * @param array $keys Ключи для проверки в массиве * @param iterable $array Проверяемый массив, итератор или приводимый к массиву объект * * @return void */ protected function assertArrayNotHasKeys(array $keys, iterable $array): void { foreach ($keys as $key) { $this->assertArrayNotHasKey($key, $array); } } /** * Утверждает, что в объекте отсутствуют все указанные свойства * * @param array $props Свойства для проверки в объекте * @param object $object Проверяемый объект * * @return void */ protected function assertObjectNotHasProperties(array $props, object $object): void { foreach ($props as $prop) { $this->assertObjectNotHasProperty($prop, $object); } } /** * Утверждает, что в массиве элементы только указанного типа * * @param string $type Название типа, один из возможных результатов функции gettype() * @param iterable $array Проверяемый массив, итератор или приводимый к массиву объект * * @return void */ protected function assertArrayValuesTypeOf(string $type, iterable $array): void { foreach ($array as $key => $value) { $this->assertEquals($type, gettype($value), "Failed asserting that element [{$key}] is type of {$type}"); } } /** * Утверждает, что в массив содержит только объекты (опционально -- указанного класса) * * Работает гибче {@link self::assertContainsOnlyInstancesOf()} * засчёт предварительной подготовки проверяемых данных и возможности * нестрогой проверки имени класса. * * @param mixed $array Массив для проверки * @param object|string|null $class Имя класса (если не указано, проверяется только тип) * * @return void */ protected function assertIsArrayOfObjects(mixed $array, object|string|null $class = null): void { is_object($class) && $class = $class::class; if (is_string($array) && json_validate($array)) { $array = json_decode($array); } $this->assertNotEmpty($array); if (empty($class)) { $filtered = array_filter($array, static fn ($elem) => is_object($elem)); $this->assertSame($array, $filtered, 'Failed asserting that array containts only objects'); } else { $this->assertContainsOnlyInstancesOf($class, $array); } } }