Skip to content

Commit

Permalink
feat(functions): add support for mocking function
Browse files Browse the repository at this point in the history
  • Loading branch information
rtm-ctrlz committed Sep 24, 2021
1 parent 90692a4 commit 24deaed
Show file tree
Hide file tree
Showing 17 changed files with 467 additions and 34 deletions.
2 changes: 1 addition & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ ij_php_align_multiline_binary_operation = true
ij_php_align_multiline_chained_methods = true
ij_php_align_multiline_extends_list = true
ij_php_align_multiline_for = true
ij_php_align_multiline_parameters = true
ij_php_align_multiline_parameters = false
ij_php_align_multiline_parameters_in_calls = true
ij_php_align_multiline_ternary_operation = true
ij_php_align_phpdoc_comments = true
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/vendor/
/composer.lock
/test/report-junit.xml
/test/phpunit.cache.xml
11 changes: 6 additions & 5 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,17 @@
"QratorLabs\\Smocky\\Test\\PhpUnit\\": "./test/phpunit"
},
"files": [
"./test/phpstan/runkit7.stub.php"
"./test/phpstan/runkit7.stub.php",
"./test/phpunit/Helpers/Functions.php"
]
},
"suggest": {
"ext-runkit7": "^3.0"
},
"scripts": {
"phpstan": "./vendor/bin/phpstan analyse -c test/phpstan.neon",
"phpunit": "./vendor/bin/phpunit -c test/phpunit.xml",
"phpcs": "./vendor/bin/phpcs --standard=test/phpcs.xml -s -p .",
"phpcbf": "./vendor/bin/phpcbf --standard=test/phpcs.xml -s -p ."
"phpstan": "phpstan analyse",
"phpunit": "phpunit",
"phpcs": "phpcs ",
"phpcbf": "phpcbf"
}
}
21 changes: 21 additions & 0 deletions phpcs.xml.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<ruleset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="./vendor/squizlabs/php_codesniffer/phpcs.xsd">

<!-- General options -->
<!-- see https://github.com/squizlabs/PHP_CodeSniffer/wiki/Usage -->
<arg name="basepath" value="."/>
<arg name="extensions" value="php"/>
<arg name="report" value="full"/>
<arg name="ignore" value="vendor"/>
<arg value="psv"/>
<config name="ignore_warnings_on_exit" value="1"/>

<!-- Target -->
<file>./src</file>
<file>./test</file>

<!-- Rules section -->
<rule ref="PSR12">
</rule>
</ruleset>
7 changes: 7 additions & 0 deletions phpstan.neon.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
parameters:
level: max
paths:
- ./src
- ./test
bootstrapFiles:
- ./test/phpstan/runkit7.stub.php
17 changes: 10 additions & 7 deletions test/phpunit.xml → phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,32 @@
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.5/phpunit.xsd"
backupGlobals="true"
cacheResult="false"
cacheResultFile="./test/phpunit.cache.xml"
verbose="true">
<coverage>
<include>
<directory suffix=".php">../src</directory>
<directory suffix=".php">./src</directory>
</include>
</coverage>
<testsuites>
<testsuite name="ClassMethod">
<directory>./phpunit/ClassMethod</directory>
<directory>./test/phpunit/ClassMethod</directory>
</testsuite>
<testsuite name="Functions">
<directory>./test/phpunit/Functions</directory>
</testsuite>
<testsuite name="Constant">
<directory>./phpunit/Constant</directory>
<directory>./test/phpunit/Constant</directory>
</testsuite>
<testsuite name="Smocky">
<directory>./phpunit/Smocky</directory>
<directory>./test/phpunit/Smocky</directory>
</testsuite>
<testsuite name="Phpunit">
<directory>./phpunit/Phpunit</directory>
<directory>./test/phpunit/Phpunit</directory>
</testsuite>
</testsuites>
<logging>
<testdoxText outputFile="php://stdout"/>
<junit outputFile="report-junit.xml"/>
<junit outputFile="./test/report-junit.xml"/>
</logging>
</phpunit>
39 changes: 39 additions & 0 deletions src/Functions/BaseFunction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

declare(strict_types=1);

namespace QratorLabs\Smocky\Functions;

use QratorLabs\Smocky\MockedEntity;

abstract class BaseFunction extends MockedEntity
{

/**
* @var ?string
*/
protected $namespace = null;

/** @var string */
protected $function;

/** @var string */
protected $stashedName;

/**
* BaseFunction constructor.
*
* @param string $function
*/
abstract public function __construct(string $function);

public function getShortName(): string
{
return $this->function;
}

public function getFullName(): string
{
return $this->namespace === null ? $this->function : ($this->namespace . '\\' . $this->function);
}
}
57 changes: 57 additions & 0 deletions src/Functions/MockedFunction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php

declare(strict_types=1);

namespace QratorLabs\Smocky\Functions;

use Closure;
use ReflectionException;

use function runkit7_function_add;
use function runkit7_function_remove;
use function runkit7_function_rename;

class MockedFunction extends UndefinedFunction
{

/** @var Closure */
protected $closure;

/**
* @param string $function
* @param Closure|null $closure
*
* @throws ReflectionException
*/
public function __construct(string $function, Closure $closure = null)
{
$this->closure =
$closure ??
static function (): void {
};

parent::__construct($function);
$closure = $this->closure;
$tmpName = $this->getStashedName($this->function);
runkit7_function_add(
$tmpName,
/**
* @param array<mixed> $args
*
* @return mixed
*/
static function (...$args) use ($closure) {
return $closure(...$args);
}
);
runkit7_function_rename($tmpName, $this->getFullName());
}

public function __destruct()
{
if (isset($this->stashedName)) {
runkit7_function_remove($this->getFullName());
parent::__destruct();
}
}
}
45 changes: 45 additions & 0 deletions src/Functions/UndefinedFunction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

declare(strict_types=1);

namespace QratorLabs\Smocky\Functions;

use ReflectionException;
use ReflectionFunction;

use function runkit7_function_rename;

class UndefinedFunction extends BaseFunction
{

/**
* @param string $function
*
* @throws ReflectionException
*/
public function __construct(string $function)
{
try {
$reflection = new ReflectionFunction($function);
} catch (ReflectionException $exception) {
throw new ReflectionException(
'Failed to create reflection: ' . $exception->getMessage(),
$exception->getCode(),
$exception
);
}

$this->namespace = $reflection->inNamespace() ? $reflection->getNamespaceName() : null;
$this->function = $reflection->getShortName();

$this->stashedName = $this->getStashedName($this->function);
runkit7_function_rename($this->getFullName(), $this->stashedName);
}

public function __destruct()
{
if (isset($this->stashedName)) {
runkit7_function_rename($this->stashedName, $this->getFullName());
}
}
}
73 changes: 73 additions & 0 deletions src/Phpunit/MockedFunction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?php

declare(strict_types=1);

namespace QratorLabs\Smocky\Phpunit;

use PHPUnit\Framework\MockObject\Builder\InvocationMocker;
use PHPUnit\Framework\MockObject\Rule\InvocationOrder;
use PHPUnit\Framework\TestCase;
use QratorLabs\Smocky\EmptyClass;
use QratorLabs\Smocky\Functions\MockedFunction as GenericMockedFunction;
use ReflectionException;

class MockedFunction
{
/** @var GenericMockedFunction */
private $mockedFunction;

/** @var InvocationMocker */
private $invocationMocker;

/**
* MockedMethod constructor.
*
* @param TestCase $testCase
* @param string $function
* @param InvocationOrder|null $invocationRule
*
* @throws ReflectionException
*
* @noinspection UnusedConstructorDependenciesInspection
*/
public function __construct(
TestCase $testCase,
string $function,
InvocationOrder $invocationRule = null
) {
$mockObject = null;
$method = null;

$this->mockedFunction = new GenericMockedFunction(
$function,
/**
* @param array<mixed> $args
*
* @return mixed
*/
static function (...$args) use (&$mockObject, &$method) {
return $mockObject->{$method}(...$args);
}
);

$method = $this->mockedFunction->getShortName();
$mockObject = $testCase->getMockBuilder(EmptyClass::class)
->disableOriginalConstructor()
->disableOriginalClone()
->disableArgumentCloning()
->disallowMockingUnknownTypes()
->addMethods([$method])
->getMock();

if ($invocationRule === null) {
$this->invocationMocker = $mockObject->method($method);
} else {
$this->invocationMocker = $mockObject->expects($invocationRule)->method($method);
}
}

public function getMocker(): InvocationMocker
{
return $this->invocationMocker;
}
}
14 changes: 0 additions & 14 deletions test/phpcs.xml

This file was deleted.

7 changes: 0 additions & 7 deletions test/phpstan.neon

This file was deleted.

33 changes: 33 additions & 0 deletions test/phpstan/runkit7.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -131,4 +131,37 @@ function runkit7_method_rename(string $classname, string $methodname, string $ne
{
return false;
}

/**
* @param string $function_name
* @param Closure|string $argument_list_or_closure
* @param string|null $code_or_doc_comment
* @param bool|null $return_by_reference
* @param string|null $doc_comment
* @param string|null $return_type
* @param bool|null $is_strict
*
* @return bool
*/
function runkit7_function_add(
string $function_name,
$argument_list_or_closure,
?string $code_or_doc_comment = null,
?bool $return_by_reference = null,
?string $doc_comment = null,
?string $return_type = null,
?bool $is_strict = null
): bool {
return false;
}

function runkit7_function_rename(string $source_name, string $target_name): bool
{
return false;
}

function runkit7_function_remove(string $function_name): bool
{
return false;
}
}
Loading

0 comments on commit 24deaed

Please sign in to comment.