LimeMockInvocationExpectation.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462
  1. <?php
  2. /*
  3. * This file is part of the Lime test framework.
  4. *
  5. * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
  6. * (c) Bernhard Schussek <bernhard.schussek@symfony-project.com>
  7. *
  8. * This source file is subject to the MIT license that is bundled
  9. * with this source code in the file LICENSE.
  10. */
  11. /**
  12. * Represents an expected method invocation.
  13. *
  14. * Instances of this class are returned when you record a new method call on
  15. * a mock object while in record mode. You can then use this instance specify
  16. * further modifiers, like how often the method is expected to be called,
  17. * whether it is expected to be called with the same parameter types etc.
  18. *
  19. * The modifiers of this class support method chaining.
  20. *
  21. * <code>
  22. * $mock = LimeMock::create('MyClass', $output);
  23. * $mock->doSomething();
  24. * // returns LimeMockInvocationExpectation
  25. *
  26. * // let's use the returned object to configure the invocation
  27. * $mock->doSomething()->atLeastOnce()->returns('some value');
  28. * </code>
  29. *
  30. * You must inform this object of an invoked method by calling invoke(). When
  31. * that is done, you can use verify() to find out whether all the modifiers
  32. * succeeded, i.e. whether the method was called a sufficient number of times
  33. * etc. The results of the verification are then written to the output.
  34. *
  35. * Note: This class is implemented to verify a method automatically upon
  36. * invoking. If all the method modifiers are satisfied, the success message
  37. * is immediately printed to the output, even if you don't call verify(). If
  38. * you want to suppress all output, you should pass an instance of LimeOutputNone
  39. * to this class.
  40. *
  41. * @package Lime
  42. * @author Bernhard Schussek <bernhard.schussek@symfony-project.com>
  43. * @version SVN: $Id: LimeMockInvocationExpectation.php 23880 2009-11-14 10:14:34Z bschussek $
  44. */
  45. class LimeMockInvocationExpectation
  46. {
  47. protected
  48. $invocation = null,
  49. $matched = false,
  50. $output = null,
  51. $countMatcher = null,
  52. $parameterMatchers = array(),
  53. $parameters = array(),
  54. $withAnyParameters = false,
  55. $returns = false,
  56. $returnValue = null,
  57. $exception = null,
  58. $callback = null,
  59. $strict = false,
  60. $verified = false;
  61. /**
  62. * Constructor.
  63. *
  64. * @param LimeMockInvocation $invocation The expected method invocation
  65. * @param LimeOutputInterface $output The output to write at when
  66. * verification passes or fails
  67. */
  68. public function __construct(LimeMockInvocation $invocation, LimeOutputInterface $output)
  69. {
  70. $this->invocation = $invocation;
  71. $this->output = $output;
  72. }
  73. protected function getMatchers()
  74. {
  75. return array_merge($this->parameterMatchers, ($this->countMatcher ? array($this->countMatcher) : array()));
  76. }
  77. /**
  78. * Returns the string representation.
  79. *
  80. * The string representation consists of the method name and the messages of
  81. * all applied modifiers.
  82. *
  83. * Example:
  84. *
  85. * "doSomething() was called at least once"
  86. *
  87. * @return string
  88. */
  89. public function __toString()
  90. {
  91. $string = $this->invocation.' was called';
  92. foreach ($this->getMatchers() as $matcher)
  93. {
  94. // avoid trailing spaces if the message is empty
  95. $string = rtrim($string.' '.$matcher->getMessage());
  96. }
  97. return $string;
  98. }
  99. /**
  100. * Notifies this object of the given method invocation.
  101. *
  102. * If any of the matchers decides that this method should not have been
  103. * invoked, an exception is thrown. If all matchers are satisfied, a success
  104. * message is printed to the output.
  105. *
  106. * If this object was configured to throw an exception, this exception is
  107. * thrown now. Otherwise the method's configured return value is returned.
  108. *
  109. * @param LimeMockInvocation $invocation The invoked method
  110. * @return mixed The configured return value
  111. * See returns()
  112. * @throws LimeMockInvocationException If the method should not have been
  113. * invoked
  114. * @throws Exception If this object was configured to
  115. * throw an exception
  116. * See throw()
  117. */
  118. public function invoke(LimeMockInvocation $invocation)
  119. {
  120. try
  121. {
  122. foreach ($this->getMatchers() as $matcher)
  123. {
  124. $matcher->invoke($invocation);
  125. }
  126. }
  127. catch (LimeMockInvocationMatcherException $e)
  128. {
  129. throw new LimeMockInvocationException($this->invocation, $e->getMessage());
  130. }
  131. if (!$this->verified && $this->isSatisfied())
  132. {
  133. list ($file, $line) = LimeTrace::findCaller('LimeMockInterface');
  134. $this->output->pass((string)$this, $file, $line);
  135. $this->verified = true;
  136. }
  137. if (!is_null($this->callback))
  138. {
  139. $result = call_user_func_array($this->callback, $invocation->getParameters());
  140. return $this->returns ? $this->returnValue : $result;
  141. }
  142. if (!is_null($this->exception))
  143. {
  144. if (is_string($this->exception))
  145. {
  146. throw new $this->exception();
  147. }
  148. else
  149. {
  150. throw $this->exception;
  151. }
  152. }
  153. return $this->returnValue;
  154. }
  155. /**
  156. * Returns whether the method signature and parameters of this object match
  157. * the given invocation.
  158. *
  159. * @param LimeMockInvocation $invocation
  160. * @return boolean
  161. */
  162. public function matches(LimeMockMethodInterface $method)
  163. {
  164. if ($this->invocation->getClass() != $method->getClass() || $this->invocation->getMethod() != $method->getMethod())
  165. {
  166. return false;
  167. }
  168. else if ($method instanceof LimeMockInvocation && !$this->withAnyParameters)
  169. {
  170. $index = 0;
  171. foreach ($this->parameterMatchers as $matcher)
  172. {
  173. $index = max($index, $matcher->getIndex());
  174. }
  175. return count($method->getParameters()) == $index;
  176. }
  177. else
  178. {
  179. return true;
  180. }
  181. }
  182. /**
  183. * Returns whether this object may be invoked.
  184. *
  185. * This method returns FALSE if the next call to invoke() would throw a
  186. * LimeMockInvocationException.
  187. *
  188. * @return boolean
  189. */
  190. public function isInvokable()
  191. {
  192. $result = true;
  193. foreach ($this->getMatchers() as $matcher)
  194. {
  195. $result = $result && $matcher->isInvokable();
  196. }
  197. return $result;
  198. }
  199. /**
  200. * Returns whether the requirements of all configured modifiers have been
  201. * fulfilled.
  202. *
  203. * @return boolean
  204. */
  205. public function isSatisfied()
  206. {
  207. $result = true;
  208. foreach ($this->getMatchers() as $matcher)
  209. {
  210. $result = $result && $matcher->isSatisfied();
  211. }
  212. return $result;
  213. }
  214. /**
  215. * Verifies whether the requirements of all configured modifiers have been
  216. * fulfilled.
  217. *
  218. * Depending on the result, either a failed or a passed test is written to the
  219. * output. A method may only be verified once.
  220. *
  221. * Note: Methods are verified automatically once invoke() is called and
  222. * all matchers are satisfied. In this case verify() simply does nothing.
  223. */
  224. public function verify()
  225. {
  226. if (!$this->verified)
  227. {
  228. list ($file, $line) = LimeTrace::findCaller('LimeMockInterface');
  229. if ($this->isSatisfied())
  230. {
  231. $this->output->pass((string)$this, $file, $line);
  232. }
  233. else
  234. {
  235. $this->output->fail((string)$this, $file, $line);
  236. }
  237. $this->verified = true;
  238. }
  239. }
  240. /**
  241. * This method is expected to be called the given number of times.
  242. *
  243. * @param integer $times
  244. * @return LimeMockInvocationExpectation This object
  245. */
  246. public function times($times)
  247. {
  248. $this->countMatcher = new LimeMockInvocationMatcherTimes($times);
  249. return $this;
  250. }
  251. /**
  252. * This method is expected to be called exactly once.
  253. *
  254. * @return LimeMockInvocationExpectation This object
  255. */
  256. public function once()
  257. {
  258. return $this->times(1);
  259. }
  260. /**
  261. * This method is expected to be called never.
  262. *
  263. * @return LimeMockInvocationExpectation This object
  264. */
  265. public function never()
  266. {
  267. return $this->times(0);
  268. }
  269. /**
  270. * This method is expected to be called zero times or more.
  271. *
  272. * @return LimeMockInvocationExpectation This object
  273. */
  274. public function any()
  275. {
  276. $this->countMatcher = new LimeMockInvocationMatcherAny();
  277. return $this;
  278. }
  279. /**
  280. * This method is expected to be called once or more.
  281. *
  282. * @return LimeMockInvocationExpectation This object
  283. */
  284. public function atLeastOnce()
  285. {
  286. $this->countMatcher = new LimeMockInvocationMatcherAtLeastOnce();
  287. return $this;
  288. }
  289. /**
  290. * This method is expected to be called any time within the given limits.
  291. *
  292. * The limits are inclusive. If the method is called exactly $start times,
  293. * the requirements of this modifier are fulfilled.
  294. *
  295. * @param integer $start
  296. * @param integer $end
  297. * @return LimeMockInvocationExpectation This object
  298. */
  299. public function between($start, $end)
  300. {
  301. $this->countMatcher = new LimeMockInvocationMatcherBetween($start, $end);
  302. return $this;
  303. }
  304. /**
  305. * This method will return the given value when invoked.
  306. *
  307. * @param mixed $value
  308. * @return LimeMockInvocationExpectation This object
  309. */
  310. public function returns($value)
  311. {
  312. $this->returns = true;
  313. $this->returnValue = $value;
  314. return $this;
  315. }
  316. /**
  317. * This method will throw the given exception when invoked.
  318. *
  319. * @param string|Exception $class
  320. * @return LimeMockInvocationExpectation This object
  321. */
  322. public function throws($class)
  323. {
  324. $this->exception = $class;
  325. return $this;
  326. }
  327. /**
  328. * This method will call the given callback and return its return value when
  329. * invoked.
  330. *
  331. * @param callable $callback
  332. * @return LimeMockInvocationExpectation This object
  333. */
  334. public function callback($callback)
  335. {
  336. if (!is_callable($callback))
  337. {
  338. throw new InvalidArgumentException('The given argument is no callable');
  339. }
  340. $this->callback = $callback;
  341. return $this;
  342. }
  343. /**
  344. * This method must be called with the exact same parameter types.
  345. *
  346. * @return LimeMockInvocationExpectation This object
  347. */
  348. public function strict()
  349. {
  350. $this->strict = true;
  351. if (!$this->withAnyParameters)
  352. {
  353. // reload matchers
  354. $this->withParameters($this->parameters);
  355. }
  356. return $this;
  357. }
  358. /**
  359. * Configures a parameter to match some constraint.
  360. *
  361. * The constraint can be configured on the returned matcher object.
  362. *
  363. * @param integer $index The index of the parameter. The first parameter has
  364. * index 1.
  365. * @return LimeMockInvocationMatcherParameter
  366. */
  367. public function parameter($index)
  368. {
  369. $this->parameterMatchers[$index] = $matcher = new LimeMockInvocationMatcherParameter($index, $this);
  370. return $matcher;
  371. }
  372. /**
  373. * This method can be called with any parameters.
  374. *
  375. * @return LimeMockInvocationExpectation This object
  376. */
  377. public function withAnyParameters()
  378. {
  379. $this->parameterMatchers = array();
  380. $this->withAnyParameters = true;
  381. return $this;
  382. }
  383. /**
  384. * This method must be called with the given parameters.
  385. *
  386. * @param array $parameters
  387. * @param $strict
  388. * @return unknown_type
  389. */
  390. public function withParameters(array $parameters)
  391. {
  392. $this->parameters = $parameters;
  393. $this->parameterMatchers = array();
  394. $this->withAnyParameters = false;
  395. foreach ($parameters as $index => $value)
  396. {
  397. if ($this->strict)
  398. {
  399. $this->parameter($index+1)->same($value);
  400. }
  401. else
  402. {
  403. $this->parameter($index+1)->is($value);
  404. }
  405. }
  406. return $this;
  407. }
  408. }