5 * Copyright (c) 2002-2009, Sebastian Bergmann <sb@sebastian-bergmann.de>.
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
12 * * Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
15 * * Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in
17 * the documentation and/or other materials provided with the
20 * * Neither the name of Sebastian Bergmann nor the names of his
21 * contributors may be used to endorse or promote products derived
22 * from this software without specific prior written permission.
24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
25 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
26 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
27 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
28 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
29 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
30 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
31 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
32 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
33 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
34 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
35 * POSSIBILITY OF SUCH DAMAGE.
39 * @author Jan Borsodi <jb@ez.no>
40 * @author Sebastian Bergmann <sb@sebastian-bergmann.de>
41 * @copyright 2002-2009 Sebastian Bergmann <sb@sebastian-bergmann.de>
42 * @license http://www.opensource.org/licenses/bsd-license.php BSD License
44 * @link http://www.phpunit.de/
45 * @since File available since Release 3.0.0
48 require_once 'PHPUnit/Util/Class.php';
49 require_once 'PHPUnit/Util/Filter.php';
50 require_once 'PHPUnit/Framework/MockObject/Matcher.php';
51 require_once 'PHPUnit/Framework/MockObject/Invocation.php';
52 require_once 'PHPUnit/Framework/MockObject/MockObject.php';
54 PHPUnit_Util_Filter::addFileToFilter(__FILE__, 'PHPUNIT');
57 * Provides generation of mock classes and objects from existing classes.
59 * The mocked class will contain all the methods of the original class but with
60 * a different implementation which will call the current
61 * PHPUnit_Framework_MockObject_InvocationMocker object, this objects takes
62 * care of checking expectations and stubs.
63 * It is also possible to define which methods are mocked by passing an array
66 * The simplest way to define a mock object is to do:
69 * PHPUnit_Framework_MockObject_Mock::generate('MyClass');
70 * $o = new Mock_MyClass;
73 * The generate() method returns an object which can be queried.
76 * $m = PHPUnit_Framework_MockObject::generate('MyClass');
77 * $o = new $m->mockClassName;
78 * print "original class was: . $m->className;
83 * @author Jan Borsodi <jb@ez.no>
84 * @author Sebastian Bergmann <sb@sebastian-bergmann.de>
85 * @copyright 2002-2009 Sebastian Bergmann <sb@sebastian-bergmann.de>
86 * @license http://www.opensource.org/licenses/bsd-license.php BSD License
87 * @version Release: 3.3.17
88 * @link http://www.phpunit.de/
89 * @since Class available since Release 3.0.0
91 class PHPUnit_Framework_MockObject_Mock
93 public $mockClassName;
95 public $fullClassName;
96 public $namespaceName;
98 protected $callOriginalConstructor;
99 protected $callOriginalClone;
100 protected $callAutoload;
101 protected static $cache = array();
103 public function __construct($className, $methods = array(), $mockClassName = '', $callOriginalConstructor = TRUE, $callOriginalClone = TRUE, $callAutoload = TRUE)
105 $classNameParts = explode('\\', $className);
107 if (count($classNameParts) > 1) {
108 $className = array_pop($classNameParts);
109 $namespaceName = join('\\', $classNameParts);
110 $this->fullClassName = $namespaceName . '\\' . $className;
113 $this->fullClassName = $className;
116 if ($mockClassName === '') {
118 $mockClassName = 'Mock_' . $className . '_' . substr(md5(microtime()), 0, 8);
120 while (class_exists($mockClassName, FALSE));
123 else if (class_exists($mockClassName, FALSE)) {
124 throw new RuntimeException(
126 'Class "%s" already exists.',
132 $isClass = class_exists($className, $callAutoload);
133 $isInterface = interface_exists($className, $callAutoload);
135 if (is_array($methods) && empty($methods) && ($isClass || $isInterface)) {
136 $methods = get_class_methods($className);
140 $callOriginalConstructor = FALSE;
143 $this->mockClassName = $mockClassName;
144 $this->className = $className;
145 $this->namespaceName = $namespaceName;
146 $this->methods = $methods;
147 $this->callOriginalConstructor = $callOriginalConstructor;
148 $this->callOriginalClone = $callOriginalClone;
149 $this->callAutoload = $callAutoload;
152 public static function generate($className, $methods = array(), $mockClassName = '', $callOriginalConstructor = TRUE, $callOriginalClone = TRUE, $callAutoload = TRUE)
154 if ($mockClassName == '') {
157 serialize($methods) .
158 serialize($callOriginalConstructor) .
159 serialize($callOriginalClone)
162 if (!isset(self::$cache[$key])) {
163 self::$cache[$key] = self::generateMock(
167 $callOriginalConstructor,
173 return self::$cache[$key];
176 return self::generateMock(
180 $callOriginalConstructor,
186 protected static function generateMock($className, $methods, $mockClassName, $callOriginalConstructor, $callOriginalClone, $callAutoload)
188 $mock = new PHPUnit_Framework_MockObject_Mock(
192 $callOriginalConstructor,
197 $mock->generateClass();
202 protected function generateClass()
204 if (!class_exists($this->fullClassName, $this->callAutoload) && !interface_exists($this->fullClassName, $this->callAutoload)) {
205 $code = 'class ' . $this->className . ' {}';
207 if (!empty($this->namespaceName)) {
208 $code = 'namespace ' . $this->namespaceName . ';' . $code;
215 $class = new ReflectionClass($this->fullClassName);
217 if ($class->isFinal()) {
218 throw new RuntimeException(
220 'Class "%s" is declared "final" and cannot be mocked.',
226 $code = $this->generateClassDefinition($class);
231 catch (Exception $e) {
232 throw new RuntimeException(
234 'Failed to generate mock class "%s" for class "%s".\n%s',
235 $this->mockClassName,
236 $this->fullClassName,
243 protected function generateClassDefinition(ReflectionClass $class)
247 if ($class->isInterface()) {
249 "%s implements %s%s {\n",
250 $this->mockClassName,
251 !empty($this->namespaceName) ? $this->namespaceName . '\\' : '',
256 "%s extends %s%s {\n",
257 $this->mockClassName,
258 !empty($this->namespaceName) ? $this->namespaceName . '\\' : '',
263 $code .= $this->generateMockApi($class);
265 if (is_array($this->methods)) {
266 foreach ($this->methods as $methodName) {
268 $method = $class->getMethod($methodName);
270 if ($this->canMockMethod($method)) {
271 $code .= $this->generateMethodDefinitionFromExisting($method);
275 catch (ReflectionException $e) {
276 $code .= $this->generateMethodDefinition($class->getName(), $methodName, 'public');
286 protected function canMockMethod(ReflectionMethod $method)
288 $className = $method->getDeclaringClass()->getName();
289 $methodName = $method->getName();
291 if ($method->isFinal() || $method->isStatic() ||
292 $methodName == '__construct' || $methodName == $className ||
293 $methodName == '__destruct' || $method->getName() == '__clone') {
300 protected function generateMethodDefinitionFromExisting(ReflectionMethod $method)
302 if ($method->isPrivate()) {
303 $modifier = 'private';
306 else if ($method->isProtected()) {
307 $modifier = 'protected';
311 $modifier = 'public';
314 if ($method->returnsReference()) {
320 return $this->generateMethodDefinition(
321 $method->getDeclaringClass()->getName(),
325 PHPUnit_Util_Class::getMethodParameters($method)
329 protected function generateMethodDefinition($className, $methodName, $modifier, $reference = '', $parameters = '')
332 "\n %s function %s%s(%s) {\n" .
333 " \$args = func_get_args();\n" .
334 " \$result = \$this->invocationMocker->invoke(\n" .
335 " new PHPUnit_Framework_MockObject_Invocation(\$this, \"%s\", \"%s\", \$args)\n" .
337 " return \$result;\n" .
349 protected function generateMockApi(ReflectionClass $class)
351 if ($this->callOriginalConstructor) {
352 $constructorCode = $this->generateConstructorCodeWithParentCall($class);
354 $constructorCode = $this->generateConstructorCode($class);
357 if ($this->callOriginalClone && $class->hasMethod('__clone')) {
358 $cloneCode = $this->generateCloneCodeWithParentCall();
360 $cloneCode = $this->generateCloneCode();
364 " private \$invocationMocker;\n\n" .
367 " public function expects(PHPUnit_Framework_MockObject_Matcher_Invocation \$matcher) {\n" .
368 " return \$this->invocationMocker->expects(\$matcher);\n" .
370 " public function __phpunit_getInvocationMocker() {\n" .
371 " return \$this->invocationMocker;\n" .
373 " public function __phpunit_verify() {\n" .
374 " \$this->invocationMocker->verify();\n" .
382 protected function generateConstructorCode(ReflectionClass $class)
385 $constructor = $class->getConstructor();
387 if ($constructor !== NULL) {
388 $constructorName = $constructor->getName();
390 foreach (PHPUnit_Util_Class::getHierarchy($class->getName(), TRUE) as $_class) {
391 foreach ($_class->getInterfaces() as $interface) {
392 if ($interface->hasMethod($constructorName)) {
393 $arguments = PHPUnit_Util_Class::getMethodParameters($constructor);
401 " public function __construct(%s) {\n" .
402 " \$this->invocationMocker = new PHPUnit_Framework_MockObject_InvocationMocker(\$this);\n" .
409 protected function generateConstructorCodeWithParentCall(ReflectionClass $class)
411 $constructor = $class->getConstructor();
413 if ($constructor !== NULL) {
415 " public function __construct(%s) {\n" .
416 " \$args = func_get_args();\n" .
417 " \$this->invocationMocker = new PHPUnit_Framework_MockObject_InvocationMocker;\n" .
418 " \$class = new ReflectionClass('%s');\n" .
419 " \$class->getParentClass()->getConstructor()->invokeArgs(\$this, \$args);\n" .
422 PHPUnit_Util_Class::getMethodParameters($constructor),
426 return $this->generateConstructorCode($class);
430 protected function generateCloneCode()
432 return " public function __clone() {\n" .
433 " \$this->invocationMocker = clone \$this->invocationMocker;\n" .
437 protected function generateCloneCodeWithParentCall()
439 return " public function __clone() {\n" .
440 " \$this->invocationMocker = clone \$this->invocationMocker;\n" .
441 " parent::__clone();\n" .