diff --git a/composer.json b/composer.json
index 44f6b50..b74341f 100644
--- a/composer.json
+++ b/composer.json
@@ -3,10 +3,10 @@
"description": "Allows mocking otherwise untestable PHP functions through the use of namespaces",
"license": "MIT",
"require": {
- "php": "~7"
+ "php": "~7.1"
},
"require-dev": {
- "phpunit/phpunit": "~6"
+ "phpunit/phpunit": "~7"
},
"authors": [
{
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index 435a1a3..d7ba5c5 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -3,9 +3,7 @@
convertErrorsToExceptions="true"
convertWarningsToExceptions="true"
convertNoticesToExceptions="true"
- mapTestClassNameToCoveredClassName="true"
bootstrap="vendor/autoload.php"
- strict="true"
verbose="true"
colors="true">
@@ -16,18 +14,6 @@
-
-
-
+
-
-
-
- tests/
- vendor/
- /usr/share/php
-
-
diff --git a/src/PHPUnit/Extension/FunctionMocker.php b/src/PHPUnit/Extension/FunctionMocker.php
index 7d46ddf..7141d4d 100644
--- a/src/PHPUnit/Extension/FunctionMocker.php
+++ b/src/PHPUnit/Extension/FunctionMocker.php
@@ -2,7 +2,10 @@
namespace PHPUnit\Extension;
use PHPUnit\Extension\FunctionMocker\CodeGenerator;
+use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
+use function bin2hex;
+use function random_bytes;
class FunctionMocker
{
@@ -15,6 +18,9 @@ class FunctionMocker
/** @var array */
private $functions = array();
+ /** @var array */
+ private $constants = [];
+
/** @var array */
private static $mockedFunctions = array();
@@ -30,22 +36,18 @@ private function __construct(TestCase $testCase, $namespace)
* Example: PHP global namespace function setcookie() needs to be overridden in order to test
* if a cookie gets set. When setcookie() is called from inside a class in the namespace
* \Foo\Bar the mock setcookie() created here will be used instead to the real function.
- *
- * @param TestCase $testCase
- * @param string $namespace
- * @return FunctionMocker
*/
- public static function start(TestCase $testCase, $namespace)
+ public static function start(TestCase $testCase, string $namespace): self
{
return new static($testCase, $namespace);
}
- public static function tearDown()
+ public static function tearDown(): void
{
unset($GLOBALS['__PHPUNIT_EXTENSION_FUNCTIONMOCKER']);
}
- public function mockFunction($function)
+ public function mockFunction(string $function): self
{
$function = trim(strtolower($function));
@@ -56,40 +58,31 @@ public function mockFunction($function)
return $this;
}
- public function getMock()
+ public function mockConstant(string $constant, $value): self
+ {
+ $this->constants[trim($constant)] = $value;
+
+ return $this;
+ }
+
+ public function getMock(): MockObject
{
$mock = $this->testCase->getMockBuilder('stdClass')
->setMethods($this->functions)
- ->setMockClassName('PHPUnit_Extension_FunctionMocker_' . uniqid())
+ ->setMockClassName('PHPUnit_Extension_FunctionMocker_' . bin2hex(random_bytes(16)))
->getMock();
+ foreach ($this->constants as $constant => $value) {
+ CodeGenerator::defineConstant($this->namespace, $constant, $value);
+ }
+
foreach ($this->functions as $function) {
$fqFunction = $this->namespace . '\\' . $function;
if (in_array($fqFunction, static::$mockedFunctions, true)) {
continue;
}
- if (!extension_loaded('runkit') || !ini_get('runkit.internal_override')) {
- CodeGenerator::defineFunction($function, $this->namespace);
- } elseif (!function_exists('__phpunit_function_mocker_' . $function)) {
- runkit_function_rename($function, '__phpunit_function_mocker_' . $function);
- error_log($function);
- runkit_method_redefine(
- $function,
- function () use ($function) {
- if (!isset($GLOBALS['__PHPUNIT_EXTENSION_FUNCTIONMOCKER'][$this->namespace])) {
- return call_user_func_array('__phpunit_function_mocker_' . $function, func_get_args());
- }
-
- return call_user_func_array(
- array($GLOBALS['__PHPUNIT_EXTENSION_FUNCTIONMOCKER'][$this->namespace], $function),
- func_get_args()
- );
- }
- );
- var_dump(strlen("foo"));
- }
-
+ CodeGenerator::defineFunction($this->namespace, $function);
static::$mockedFunctions[] = $fqFunction;
}
diff --git a/src/PHPUnit/Extension/FunctionMocker/CodeGenerator.php b/src/PHPUnit/Extension/FunctionMocker/CodeGenerator.php
index e401178..62e443b 100644
--- a/src/PHPUnit/Extension/FunctionMocker/CodeGenerator.php
+++ b/src/PHPUnit/Extension/FunctionMocker/CodeGenerator.php
@@ -1,33 +1,78 @@
{function}(...$args);
}
}
EOS;
- return sprintf($template, $namespaceName, $functionName);
+ return self::renderTemplate($template, ['namespace' => $namespace, 'function' => $function]);
}
- public static function defineFunction($functionName, $namespaceName)
+ public static function defineFunction(string $namespace, string $function): void
{
- $code = static::generateCode($functionName, $namespaceName);
+ $code = static::generateFunction($namespace, $function);
eval($code);
}
+
+ public static function generateConstant($namespace, $constant, $value)
+ {
+ $template = <<<'EOS'
+namespace {namespace}
+{
+ if (!defined(__NAMESPACE__ . '\\{constant}')) {
+ define(__NAMESPACE__ . '\\{constant}', {value});
+ } elseif ({constant} !== {value}) {
+ throw new \RuntimeException(sprintf('Cannot redeclare constant "{constant}" in namespace "%s". Already defined as "%s"', __NAMESPACE__, {value}));
+ }
+}
+EOS;
+
+ return self::renderTemplate(
+ $template,
+ [
+ 'namespace' => $namespace,
+ 'constant' => $constant,
+ 'value' => var_export($value, true),
+ ]
+ );
+ }
+
+ public static function defineConstant(string $namespace, string $name, string $value): void
+ {
+ eval(self::generateConstant($namespace, $name, $value));
+ }
+
+ private static function renderTemplate(string $template, array $parameters): string
+ {
+ return strtr(
+ $template,
+ array_combine(
+ array_map(
+ function (string $key): string {
+ return '{' . $key . '}';
+ },
+ array_keys($parameters)
+ ),
+ array_values($parameters)
+ )
+ );
+ }
}
diff --git a/tests/PHPUnitTests/Extension/Fixtures/TestClass.php b/tests/PHPUnitTests/Extension/Fixtures/TestClass.php
index ee52c6a..716f130 100644
--- a/tests/PHPUnitTests/Extension/Fixtures/TestClass.php
+++ b/tests/PHPUnitTests/Extension/Fixtures/TestClass.php
@@ -7,4 +7,9 @@ public static function invokeGlobalFunction()
{
return strpos('ffoo', 'o');
}
+
+ public static function getGlobalConstant()
+ {
+ return CNT;
+ }
}
diff --git a/tests/PHPUnitTests/Extension/FunctionMocker/CodeGeneratorTest.php b/tests/PHPUnitTests/Extension/FunctionMocker/CodeGeneratorTest.php
index 8b01430..9ee8fe1 100644
--- a/tests/PHPUnitTests/Extension/FunctionMocker/CodeGeneratorTest.php
+++ b/tests/PHPUnitTests/Extension/FunctionMocker/CodeGeneratorTest.php
@@ -6,26 +6,57 @@
class CodeGeneratorTest extends TestCase
{
- public function testRetrieveSimpleFunctionMock()
+ public function testGenerateFunctionMock()
{
- $code = CodeGenerator::generateCode('strlen', 'Test\Namespace');
+ $code = CodeGenerator::generateFunction('Test\Namespace', 'strlen');
$expected = <<<'EOS'
namespace Test\Namespace
{
- function strlen()
+ function strlen(...$args)
{
- if (!isset($GLOBALS['__PHPUNIT_EXTENSION_FUNCTIONMOCKER']['Test\Namespace'])) {
- return call_user_func_array('strlen', func_get_args());
+ if (!isset($GLOBALS['__PHPUNIT_EXTENSION_FUNCTIONMOCKER'][__NAMESPACE__])) {
+ return \strlen(...$args);
}
- return call_user_func_array(
- array($GLOBALS['__PHPUNIT_EXTENSION_FUNCTIONMOCKER']['Test\Namespace'], 'strlen'),
- func_get_args()
- );
+ return $GLOBALS['__PHPUNIT_EXTENSION_FUNCTIONMOCKER'][__NAMESPACE__]->strlen(...$args);
}
}
EOS;
- $this->assertEquals($expected, $code);
+ self::assertSame($expected, $code);
+ }
+
+ public function testGenerateStringConstantMock()
+ {
+ $code = CodeGenerator::generateConstant('Test\Namespace', 'CONSTANT', 'value');
+
+ $expected = <<<'EOS'
+namespace Test\Namespace
+{
+ if (!defined(__NAMESPACE__ . '\\CONSTANT')) {
+ define(__NAMESPACE__ . '\\CONSTANT', 'value');
+ } elseif (CONSTANT !== 'value') {
+ throw new \RuntimeException(sprintf('Cannot redeclare constant "CONSTANT" in namespace "%s". Already defined as "%s"', __NAMESPACE__, 'value'));
+ }
+}
+EOS;
+ self::assertSame($expected, $code);
+ }
+
+ public function testGenerateIntegerConstantMock(): void
+ {
+ $code = CodeGenerator::generateConstant('Test\Namespace', 'CONSTANT', 123);
+
+ $expected = <<<'EOS'
+namespace Test\Namespace
+{
+ if (!defined(__NAMESPACE__ . '\\CONSTANT')) {
+ define(__NAMESPACE__ . '\\CONSTANT', 123);
+ } elseif (CONSTANT !== 123) {
+ throw new \RuntimeException(sprintf('Cannot redeclare constant "CONSTANT" in namespace "%s". Already defined as "%s"', __NAMESPACE__, 123));
+ }
+}
+EOS;
+ self::assertSame($expected, $code);
}
}
diff --git a/tests/PHPUnitTests/Extension/FunctionMockerTest.php b/tests/PHPUnitTests/Extension/FunctionMockerTest.php
index f68bf5d..7136eb1 100644
--- a/tests/PHPUnitTests/Extension/FunctionMockerTest.php
+++ b/tests/PHPUnitTests/Extension/FunctionMockerTest.php
@@ -38,14 +38,15 @@ public function testBasicMockingFunction()
$this->assertMockFunctionDefined('My\TestNamespace\substr', 'My\TestNamespace');
$mock
- ->expects($this->once())
+ ->expects(self::once())
->method('strlen')
- ->will($this->returnValue('mocked strlen()'))
+ ->will(self::returnValue('mocked strlen()'))
;
$mock
- ->expects($this->once())
+ ->expects(self::once())
->method('substr')
- ->will($this->returnCallback(
+ ->will(
+ self::returnCallback(
function() {
return func_get_args();
}
@@ -53,8 +54,8 @@ function() {
;
$this->assertMockObjectPresent('My\TestNamespace', $mock);
- $this->assertSame('mocked strlen()', \My\TestNamespace\strlen('foo'));
- $this->assertSame(array('foo', 0, 3), \My\TestNamespace\substr('foo', 0, 3));
+ self::assertSame('mocked strlen()', \My\TestNamespace\strlen('foo'));
+ self::assertSame(array('foo', 0, 3), \My\TestNamespace\substr('foo', 0, 3));
}
public function testNamespaceLeadingAndTrailingSlash()
@@ -73,13 +74,13 @@ public function testNamespaceLeadingAndTrailingSlash()
$this->assertMockFunctionDefined('My\TestNamespace\strpos', 'My\TestNamespace');
$mock
- ->expects($this->once())
+ ->expects(self::once())
->method('strpos')
- ->will($this->returnArgument(1))
+ ->will(self::returnArgument(1))
;
$this->assertMockObjectPresent('My\TestNamespace', $mock);
- $this->assertSame('b', \My\TestNamespace\strpos('abc', 'b'));
+ self::assertSame('b', \My\TestNamespace\strpos('abc', 'b'));
}
public function testFunctionsAreUsedLowercase()
@@ -98,13 +99,13 @@ public function testFunctionsAreUsedLowercase()
$this->assertMockFunctionDefined('My\TestNamespace\myfunc', 'My\TestNamespace');
$mock
- ->expects($this->once())
+ ->expects(self::once())
->method('myfunc')
- ->will($this->returnArgument(0))
+ ->will(self::returnArgument(0))
;
$this->assertMockObjectPresent('My\TestNamespace', $mock);
- $this->assertSame('abc', \My\TestNamespace\myfunc('abc'));
+ self::assertSame('abc', \My\TestNamespace\myfunc('abc'));
}
public function testUseOneFunctionMockerMoreThanOnce()
@@ -126,19 +127,19 @@ public function testUseOneFunctionMockerMoreThanOnce()
$this->assertMockFunctionDefined('My\TestNamespace\strtr', 'My\TestNamespace');
$mock
- ->expects($this->once())
+ ->expects(self::once())
->method('strtr')
->with('abcd')
- ->will($this->returnArgument(0))
+ ->will(self::returnArgument(0))
;
$this->assertMockObjectPresent('My\TestNamespace', $mock);
try {
- $this->assertSame('abc', \My\TestNamespace\strtr('abc'));
- $this->fail('Expected exception');
+ self::assertSame('abc', \My\TestNamespace\strtr('abc'));
+ self::fail('Expected exception');
} catch (AssertionFailedError $e) {
- $this->assertContains('does not match expected value', $e->getMessage());
+ self::assertContains('does not match expected value', $e->getMessage());
}
/** Reset mock objects */
@@ -158,34 +159,34 @@ public function testMockSameFunctionIsDifferentNamespaces()
$this->assertMockFunctionDefined('My\TestNamespace\foofunc', 'My\TestNamespace');
$this->functionMocker = FunctionMocker::start($this, 'My\TestNamespace2');
- $this->assertFalse(function_exists('My\TestNamespace2\foofunc'));
+ self::assertFalse(function_exists('My\TestNamespace2\foofunc'));
$this->functionMocker
->mockFunction('foofunc');
- $this->assertFalse(function_exists('My\TestNamespace2\foofunc'));
+ self::assertFalse(function_exists('My\TestNamespace2\foofunc'));
$this->functionMocker->getMock();
$this->assertMockFunctionDefined('My\TestNamespace2\foofunc', 'My\TestNamespace2');
}
public function assertMockFunctionNotDefined($function)
{
- $this->assertFalse(
+ self::assertFalse(
function_exists($function),
sprintf('Function "%s()" was expected to be undefined', $function)
);
- $this->assertArrayNotHasKey('__PHPUNIT_EXTENSION_FUNCTIONMOCKER', $GLOBALS);
+ self::assertArrayNotHasKey('__PHPUNIT_EXTENSION_FUNCTIONMOCKER', $GLOBALS);
}
public function assertMockFunctionDefined($function, $namespace)
{
- $this->assertTrue(function_exists($function));
- $this->assertArrayHasKey('__PHPUNIT_EXTENSION_FUNCTIONMOCKER', $GLOBALS);
- $this->assertArrayHasKey($namespace, $GLOBALS['__PHPUNIT_EXTENSION_FUNCTIONMOCKER']);
+ self::assertTrue(function_exists($function));
+ self::assertArrayHasKey('__PHPUNIT_EXTENSION_FUNCTIONMOCKER', $GLOBALS);
+ self::assertArrayHasKey($namespace, $GLOBALS['__PHPUNIT_EXTENSION_FUNCTIONMOCKER']);
}
public function assertMockObjectPresent($namespace, $mock)
{
- $this->assertArrayHasKey('__PHPUNIT_EXTENSION_FUNCTIONMOCKER', $GLOBALS);
- $this->assertArrayHasKey($namespace, $GLOBALS['__PHPUNIT_EXTENSION_FUNCTIONMOCKER']);
- $this->assertSame($GLOBALS['__PHPUNIT_EXTENSION_FUNCTIONMOCKER'][$namespace], $mock);
+ self::assertArrayHasKey('__PHPUNIT_EXTENSION_FUNCTIONMOCKER', $GLOBALS);
+ self::assertArrayHasKey($namespace, $GLOBALS['__PHPUNIT_EXTENSION_FUNCTIONMOCKER']);
+ self::assertSame($GLOBALS['__PHPUNIT_EXTENSION_FUNCTIONMOCKER'][$namespace], $mock);
}
}
diff --git a/tests/PHPUnitTests/Extension/IntegrationTest.php b/tests/PHPUnitTests/Extension/IntegrationTest.php
index 7c9611b..bf0da21 100644
--- a/tests/PHPUnitTests/Extension/IntegrationTest.php
+++ b/tests/PHPUnitTests/Extension/IntegrationTest.php
@@ -3,6 +3,7 @@
use PHPUnit\Extension\FunctionMocker;
use PHPUnit\Framework\TestCase;
+use PHPUnitTests\Extension\Fixtures\TestClass;
require_once __DIR__ . '/Fixtures/TestClass.php';
@@ -14,24 +15,30 @@ public function setUp()
{
$this->php = FunctionMocker::start($this, 'PHPUnitTests\Extension\Fixtures')
->mockFunction('strpos')
+ ->mockConstant('CNT', 'val')
->getMock();
}
- public function testMocked()
+ public function testMockFunction()
{
$this->php
- ->expects($this->once())
+ ->expects(self::once())
->method('strpos')
->with('ffoo', 'o')
- ->will($this->returnValue('mocked'));
+ ->will(self::returnValue('mocked'));
- $this->assertSame('mocked', \PHPUnitTests\Extension\Fixtures\TestClass::invokeGlobalFunction());
+ self::assertSame('mocked', TestClass::invokeGlobalFunction());
}
public function testMockingGlobalFunctionAndCallingOriginalAgain()
{
- $this->testMocked();
+ $this->testMockFunction();
FunctionMocker::tearDown();
- $this->assertSame(2, \PHPUnitTests\Extension\Fixtures\TestClass::invokeGlobalFunction());
+ self::assertSame(2, TestClass::invokeGlobalFunction());
+ }
+
+ public function testMockConstant()
+ {
+ self::assertSame('val', TestClass::getGlobalConstant());
}
}