mock_objects.php
来自「PHP 知识管理系统(基于树结构的知识管理系统), 英文原版的PHP源码。」· PHP 代码 · 共 1,582 行 · 第 1/4 页
PHP
1,582 行
var $_mock_class;
var $_mock_base;
var $_reflection;
/**
* Builds initial reflection object.
* @param string $class Class to be mocked.
* @param string $mock_class New class with identical interface,
* but no behaviour.
*/
function MockGenerator($class, $mock_class) {
$this->_class = $class;
$this->_mock_class = $mock_class;
if (! $this->_mock_class) {
$this->_mock_class = 'Mock' . $this->_class;
}
$this->_mock_base = SimpleTest::getMockBaseClass();
$this->_reflection = new SimpleReflection($this->_class);
}
/**
* Clones a class' interface and creates a mock version
* that can have return values and expectations set.
* @param array $methods Additional methods to add beyond
* those in th cloned class. Use this
* to emulate the dynamic addition of
* methods in the cloned class or when
* the class hasn't been written yet.
* @access public
*/
function generate($methods) {
if (! $this->_reflection->classOrInterfaceExists()) {
return false;
}
$mock_reflection = new SimpleReflection($this->_mock_class);
if ($mock_reflection->classExistsSansAutoload()) {
return false;
}
$code = $this->_createClassCode($methods ? $methods : array());
return eval("$code return \$code;");
}
/**
* Subclasses a class and overrides every method with a mock one
* that can have return values and expectations set. Chains
* to an aggregated SimpleMock.
* @param array $methods Additional methods to add beyond
* those in the cloned class. Use this
* to emulate the dynamic addition of
* methods in the cloned class or when
* the class hasn't been written yet.
* @access public
*/
function generateSubclass($methods) {
if (! $this->_reflection->classOrInterfaceExists()) {
return false;
}
$mock_reflection = new SimpleReflection($this->_mock_class);
if ($mock_reflection->classExistsSansAutoload()) {
return false;
}
if ($this->_reflection->isInterface() || $this->_reflection->hasFinal()) {
$code = $this->_createClassCode($methods ? $methods : array());
return eval("$code return \$code;");
} else {
$code = $this->_createSubclassCode($methods ? $methods : array());
return eval("$code return \$code;");
}
}
/**
* Generates a version of a class with selected
* methods mocked only. Inherits the old class
* and chains the mock methods of an aggregated
* mock object.
* @param array $methods Methods to be overridden
* with mock versions.
* @access public
*/
function generatePartial($methods) {
if (! $this->_reflection->classExists($this->_class)) {
return false;
}
$mock_reflection = new SimpleReflection($this->_mock_class);
if ($mock_reflection->classExistsSansAutoload()) {
trigger_error('Partial mock class [' . $this->_mock_class . '] already exists');
return false;
}
$code = $this->_extendClassCode($methods);
return eval("$code return \$code;");
}
/**
* The new mock class code as a string.
* @param array $methods Additional methods.
* @return string Code for new mock class.
* @access private
*/
function _createClassCode($methods) {
$implements = '';
$interfaces = $this->_reflection->getInterfaces();
if (function_exists('spl_classes')) {
$interfaces = array_diff($interfaces, array('Traversable'));
}
if (count($interfaces) > 0) {
$implements = 'implements ' . implode(', ', $interfaces);
}
$code = "class " . $this->_mock_class . " extends " . $this->_mock_base . " $implements {\n";
$code .= " function " . $this->_mock_class . "() {\n";
$code .= " \$this->" . $this->_mock_base . "();\n";
$code .= " }\n";
if (in_array('__construct', $this->_reflection->getMethods())) {
$code .= " " . $this->_reflection->getSignature('__construct') . " {\n";
$code .= " \$this->" . $this->_mock_base . "();\n";
$code .= " }\n";
}
$code .= $this->_createHandlerCode($methods);
$code .= "}\n";
return $code;
}
/**
* The new mock class code as a string. The mock will
* be a subclass of the original mocked class.
* @param array $methods Additional methods.
* @return string Code for new mock class.
* @access private
*/
function _createSubclassCode($methods) {
$code = "class " . $this->_mock_class . " extends " . $this->_class . " {\n";
$code .= " var \$_mock;\n";
$code .= $this->_addMethodList(array_merge($methods, $this->_reflection->getMethods()));
$code .= "\n";
$code .= " function " . $this->_mock_class . "() {\n";
$code .= " \$this->_mock = &new " . $this->_mock_base . "();\n";
$code .= " \$this->_mock->disableExpectationNameChecks();\n";
$code .= " }\n";
$code .= $this->_chainMockReturns();
$code .= $this->_chainMockExpectations();
$code .= $this->_chainThrowMethods();
$code .= $this->_overrideMethods($this->_reflection->getMethods());
$code .= $this->_createNewMethodCode($methods);
$code .= "}\n";
return $code;
}
/**
* The extension class code as a string. The class
* composites a mock object and chains mocked methods
* to it.
* @param array $methods Mocked methods.
* @return string Code for a new class.
* @access private
*/
function _extendClassCode($methods) {
$code = "class " . $this->_mock_class . " extends " . $this->_class . " {\n";
$code .= " var \$_mock;\n";
$code .= $this->_addMethodList($methods);
$code .= "\n";
$code .= " function " . $this->_mock_class . "() {\n";
$code .= " \$this->_mock = &new " . $this->_mock_base . "();\n";
$code .= " \$this->_mock->disableExpectationNameChecks();\n";
$code .= " }\n";
$code .= $this->_chainMockReturns();
$code .= $this->_chainMockExpectations();
$code .= $this->_chainThrowMethods();
$code .= $this->_overrideMethods($methods);
$code .= "}\n";
return $code;
}
/**
* Creates code within a class to generate replaced
* methods. All methods call the _invoke() handler
* with the method name and the arguments in an
* array.
* @param array $methods Additional methods.
* @access private
*/
function _createHandlerCode($methods) {
$code = '';
$methods = array_merge($methods, $this->_reflection->getMethods());
foreach ($methods as $method) {
if ($this->_isConstructor($method)) {
continue;
}
$mock_reflection = new SimpleReflection($this->_mock_base);
if (in_array($method, $mock_reflection->getMethods())) {
continue;
}
$code .= " " . $this->_reflection->getSignature($method) . " {\n";
$code .= " \$args = func_get_args();\n";
$code .= " \$result = &\$this->_invoke(\"$method\", \$args);\n";
$code .= " return \$result;\n";
$code .= " }\n";
}
return $code;
}
/**
* Creates code within a class to generate a new
* methods. All methods call the _invoke() handler
* on the internal mock with the method name and
* the arguments in an array.
* @param array $methods Additional methods.
* @access private
*/
function _createNewMethodCode($methods) {
$code = '';
foreach ($methods as $method) {
if ($this->_isConstructor($method)) {
continue;
}
$mock_reflection = new SimpleReflection($this->_mock_base);
if (in_array($method, $mock_reflection->getMethods())) {
continue;
}
$code .= " " . $this->_reflection->getSignature($method) . " {\n";
$code .= " \$args = func_get_args();\n";
$code .= " \$result = &\$this->_mock->_invoke(\"$method\", \$args);\n";
$code .= " return \$result;\n";
$code .= " }\n";
}
return $code;
}
/**
* Tests to see if a special PHP method is about to
* be stubbed by mistake.
* @param string $method Method name.
* @return boolean True if special.
* @access private
*/
function _isConstructor($method) {
return in_array(
strtolower($method),
array('__construct', '__destruct'));
}
/**
* Creates a list of mocked methods for error checking.
* @param array $methods Mocked methods.
* @return string Code for a method list.
* @access private
*/
function _addMethodList($methods) {
return " var \$_mocked_methods = array('" .
implode("', '", array_map('strtolower', $methods)) .
"');\n";
}
/**
* Creates code to abandon the expectation if not mocked.
* @param string $alias Parameter name of method name.
* @return string Code for bail out.
* @access private
*/
function _bailOutIfNotMocked($alias) {
$code = " if (! in_array(strtolower($alias), \$this->_mocked_methods)) {\n";
$code .= " trigger_error(\"Method [$alias] is not mocked\");\n";
$code .= " \$null = null;\n";
$code .= " return \$null;\n";
$code .= " }\n";
return $code;
}
/**
* Creates source code for chaining to the composited
* mock object.
* @return string Code for mock set up.
* @access private
*/
function _chainMockReturns() {
$code = " function setReturnValue(\$method, \$value, \$args = false) {\n";
$code .= $this->_bailOutIfNotMocked("\$method");
$code .= " \$this->_mock->setReturnValue(\$method, \$value, \$args);\n";
$code .= " }\n";
$code .= " function setReturnValueAt(\$timing, \$method, \$value, \$args = false) {\n";
$code .= $this->_bailOutIfNotMocked("\$method");
$code .= " \$this->_mock->setReturnValueAt(\$timing, \$method, \$value, \$args);\n";
$code .= " }\n";
$code .= " function setReturnReference(\$method, &\$ref, \$args = false) {\n";
$code .= $this->_bailOutIfNotMocked("\$method");
$code .= " \$this->_mock->setReturnReference(\$method, \$ref, \$args);\n";
$code .= " }\n";
$code .= " function setReturnReferenceAt(\$timing, \$method, &\$ref, \$args = false) {\n";
$code .= $this->_bailOutIfNotMocked("\$method");
$code .= " \$this->_mock->setReturnReferenceAt(\$timing, \$method, \$ref, \$args);\n";
$code .= " }\n";
return $code;
}
/**
* Creates source code for chaining to an aggregated
* mock object.
* @return string Code for expectations.
* @access private
*/
function _chainMockExpectations() {
$code = " function expect(\$method, \$args = false, \$msg = '%s') {\n";
$code .= $this->_bailOutIfNotMocked("\$method");
$code .= " \$this->_mock->expect(\$method, \$args, \$msg);\n";
$code .= " }\n";
$code .= " function expectArguments(\$method, \$args = false, \$msg = '%s') {\n";
$code .= $this->_bailOutIfNotMocked("\$method");
$code .= " \$this->_mock->expectArguments(\$method, \$args, \$msg);\n";
$code .= " }\n";
$code .= " function expectAt(\$timing, \$method, \$args = false, \$msg = '%s') {\n";
$code .= $this->_bailOutIfNotMocked("\$method");
$code .= " \$this->_mock->expectArgumentsAt(\$timing, \$method, \$args, \$msg);\n";
$code .= " }\n";
$code .= " function expectArgumentsAt(\$timing, \$method, \$args = false, \$msg = '%s') {\n";
$code .= $this->_bailOutIfNotMocked("\$method");
$code .= " \$this->_mock->expectArgumentsAt(\$timing, \$method, \$args, \$msg);\n";
$code .= " }\n";
$code .= " function expectCallCount(\$method, \$count) {\n";
$code .= $this->_bailOutIfNotMocked("\$method");
$code .= " \$this->_mock->expectCallCount(\$method, \$count, \$msg = '%s');\n";
$code .= " }\n";
$code .= " function expectMaximumCallCount(\$method, \$count, \$msg = '%s') {\n";
$code .= $this->_bailOutIfNotMocked("\$method");
$code .= " \$this->_mock->expectMaximumCallCount(\$method, \$count, \$msg = '%s');\n";
$code .= " }\n";
$code .= " function expectMinimumCallCount(\$method, \$count, \$msg = '%s') {\n";
$code .= $this->_bailOutIfNotMocked("\$method");
$code .= " \$this->_mock->expectMinimumCallCount(\$method, \$count, \$msg = '%s');\n";
$code .= " }\n";
$code .= " function expectNever(\$method) {\n";
$code .= $this->_bailOutIfNotMocked("\$method");
$code .= " \$this->_mock->expectNever(\$method);\n";
$code .= " }\n";
$code .= " function expectOnce(\$method, \$args = false, \$msg = '%s') {\n";
$code .= $this->_bailOutIfNotMocked("\$method");
$code .= " \$this->_mock->expectOnce(\$method, \$args, \$msg);\n";
$code .= " }\n";
$code .= " function expectAtLeastOnce(\$method, \$args = false, \$msg = '%s') {\n";
$code .= $this->_bailOutIfNotMocked("\$method");
$code .= " \$this->_mock->expectAtLeastOnce(\$method, \$args, \$msg);\n";
$code .= " }\n";
$code .= " function tally() {\n";
$code .= " }\n";
return $code;
}
/**
* Adds code for chaining the throw methods.
* @return string Code for chains.
* @access private
*/
function _chainThrowMethods() {
$code = " function throwOn(\$method, \$exception = false, \$args = false) {\n";
$code .= $this->_bailOutIfNotMocked("\$method");
$code .= " \$this->_mock->throwOn(\$method, \$exception, \$args);\n";
$code .= " }\n";
$code .= " function throwAt(\$timing, \$method, \$exception = false, \$args = false) {\n";
$code .= $this->_bailOutIfNotMocked("\$method");
$code .= " \$this->_mock->throwAt(\$timing, \$method, \$exception, \$args);\n";
$code .= " }\n";
$code .= " function errorOn(\$method, \$error = 'A mock error', \$args = false, \$severity = E_USER_ERROR) {\n";
$code .= $this->_bailOutIfNotMocked("\$method");
$code .= " \$this->_mock->errorOn(\$method, \$error, \$args, \$severity);\n";
$code .= " }\n";
$code .= " function errorAt(\$timing, \$method, \$error = 'A mock error', \$args = false, \$severity = E_USER_ERROR) {\n";
$code .= $this->_bailOutIfNotMocked("\$method");
$code .= " \$this->_mock->errorAt(\$timing, \$method, \$error, \$args, \$severity);\n";
$code .= " }\n";
return $code;
}
/**
* Creates source code to override a list of methods
* with mock versions.
* @param array $methods Methods to be overridden
* with mock versions.
* @return string Code for overridden chains.
* @access private
*/
function _overrideMethods($methods) {
$code = "";
foreach ($methods as $method) {
if ($this->_isConstructor($method)) {
continue;
}
$code .= " " . $this->_reflection->getSignature($method) . " {\n";
$code .= " \$args = func_get_args();\n";
$code .= " \$result = &\$this->_mock->_invoke(\"$method\", \$args);\n";
$code .= " return \$result;\n";
$code .= " }\n";
}
return $code;
}
}
?>
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?