BusFake.php 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739
  1. <?php
  2. namespace Illuminate\Support\Testing\Fakes;
  3. use Closure;
  4. use Illuminate\Bus\PendingBatch;
  5. use Illuminate\Contracts\Bus\QueueingDispatcher;
  6. use Illuminate\Support\Arr;
  7. use Illuminate\Support\Collection;
  8. use Illuminate\Support\Traits\ReflectsClosures;
  9. use PHPUnit\Framework\Assert as PHPUnit;
  10. class BusFake implements QueueingDispatcher
  11. {
  12. use ReflectsClosures;
  13. /**
  14. * The original Bus dispatcher implementation.
  15. *
  16. * @var \Illuminate\Contracts\Bus\QueueingDispatcher
  17. */
  18. protected $dispatcher;
  19. /**
  20. * The job types that should be intercepted instead of dispatched.
  21. *
  22. * @var array
  23. */
  24. protected $jobsToFake;
  25. /**
  26. * The commands that have been dispatched.
  27. *
  28. * @var array
  29. */
  30. protected $commands = [];
  31. /**
  32. * The commands that have been dispatched synchronously.
  33. *
  34. * @var array
  35. */
  36. protected $commandsSync = [];
  37. /**
  38. * The commands that have been dispatched after the response has been sent.
  39. *
  40. * @var array
  41. */
  42. protected $commandsAfterResponse = [];
  43. /**
  44. * The batches that have been dispatched.
  45. *
  46. * @var array
  47. */
  48. protected $batches = [];
  49. /**
  50. * Create a new bus fake instance.
  51. *
  52. * @param \Illuminate\Contracts\Bus\QueueingDispatcher $dispatcher
  53. * @param array|string $jobsToFake
  54. * @return void
  55. */
  56. public function __construct(QueueingDispatcher $dispatcher, $jobsToFake = [])
  57. {
  58. $this->dispatcher = $dispatcher;
  59. $this->jobsToFake = Arr::wrap($jobsToFake);
  60. }
  61. /**
  62. * Assert if a job was dispatched based on a truth-test callback.
  63. *
  64. * @param string|\Closure $command
  65. * @param callable|int|null $callback
  66. * @return void
  67. */
  68. public function assertDispatched($command, $callback = null)
  69. {
  70. if ($command instanceof Closure) {
  71. [$command, $callback] = [$this->firstClosureParameterType($command), $command];
  72. }
  73. if (is_numeric($callback)) {
  74. return $this->assertDispatchedTimes($command, $callback);
  75. }
  76. PHPUnit::assertTrue(
  77. $this->dispatched($command, $callback)->count() > 0 ||
  78. $this->dispatchedAfterResponse($command, $callback)->count() > 0 ||
  79. $this->dispatchedSync($command, $callback)->count() > 0,
  80. "The expected [{$command}] job was not dispatched."
  81. );
  82. }
  83. /**
  84. * Assert if a job was pushed a number of times.
  85. *
  86. * @param string $command
  87. * @param int $times
  88. * @return void
  89. */
  90. public function assertDispatchedTimes($command, $times = 1)
  91. {
  92. $count = $this->dispatched($command)->count() +
  93. $this->dispatchedAfterResponse($command)->count() +
  94. $this->dispatchedSync($command)->count();
  95. PHPUnit::assertSame(
  96. $times, $count,
  97. "The expected [{$command}] job was pushed {$count} times instead of {$times} times."
  98. );
  99. }
  100. /**
  101. * Determine if a job was dispatched based on a truth-test callback.
  102. *
  103. * @param string|\Closure $command
  104. * @param callable|null $callback
  105. * @return void
  106. */
  107. public function assertNotDispatched($command, $callback = null)
  108. {
  109. if ($command instanceof Closure) {
  110. [$command, $callback] = [$this->firstClosureParameterType($command), $command];
  111. }
  112. PHPUnit::assertTrue(
  113. $this->dispatched($command, $callback)->count() === 0 &&
  114. $this->dispatchedAfterResponse($command, $callback)->count() === 0 &&
  115. $this->dispatchedSync($command, $callback)->count() === 0,
  116. "The unexpected [{$command}] job was dispatched."
  117. );
  118. }
  119. /**
  120. * Assert that no jobs were dispatched.
  121. *
  122. * @return void
  123. */
  124. public function assertNothingDispatched()
  125. {
  126. PHPUnit::assertEmpty($this->commands, 'Jobs were dispatched unexpectedly.');
  127. }
  128. /**
  129. * Assert if a job was explicitly dispatched synchronously based on a truth-test callback.
  130. *
  131. * @param string|\Closure $command
  132. * @param callable|int|null $callback
  133. * @return void
  134. */
  135. public function assertDispatchedSync($command, $callback = null)
  136. {
  137. if ($command instanceof Closure) {
  138. [$command, $callback] = [$this->firstClosureParameterType($command), $command];
  139. }
  140. if (is_numeric($callback)) {
  141. return $this->assertDispatchedSyncTimes($command, $callback);
  142. }
  143. PHPUnit::assertTrue(
  144. $this->dispatchedSync($command, $callback)->count() > 0,
  145. "The expected [{$command}] job was not dispatched synchronously."
  146. );
  147. }
  148. /**
  149. * Assert if a job was pushed synchronously a number of times.
  150. *
  151. * @param string $command
  152. * @param int $times
  153. * @return void
  154. */
  155. public function assertDispatchedSyncTimes($command, $times = 1)
  156. {
  157. $count = $this->dispatchedSync($command)->count();
  158. PHPUnit::assertSame(
  159. $times, $count,
  160. "The expected [{$command}] job was synchronously pushed {$count} times instead of {$times} times."
  161. );
  162. }
  163. /**
  164. * Determine if a job was dispatched based on a truth-test callback.
  165. *
  166. * @param string|\Closure $command
  167. * @param callable|null $callback
  168. * @return void
  169. */
  170. public function assertNotDispatchedSync($command, $callback = null)
  171. {
  172. if ($command instanceof Closure) {
  173. [$command, $callback] = [$this->firstClosureParameterType($command), $command];
  174. }
  175. PHPUnit::assertCount(
  176. 0, $this->dispatchedSync($command, $callback),
  177. "The unexpected [{$command}] job was dispatched synchronously."
  178. );
  179. }
  180. /**
  181. * Assert if a job was dispatched after the response was sent based on a truth-test callback.
  182. *
  183. * @param string|\Closure $command
  184. * @param callable|int|null $callback
  185. * @return void
  186. */
  187. public function assertDispatchedAfterResponse($command, $callback = null)
  188. {
  189. if ($command instanceof Closure) {
  190. [$command, $callback] = [$this->firstClosureParameterType($command), $command];
  191. }
  192. if (is_numeric($callback)) {
  193. return $this->assertDispatchedAfterResponseTimes($command, $callback);
  194. }
  195. PHPUnit::assertTrue(
  196. $this->dispatchedAfterResponse($command, $callback)->count() > 0,
  197. "The expected [{$command}] job was not dispatched after sending the response."
  198. );
  199. }
  200. /**
  201. * Assert if a job was pushed after the response was sent a number of times.
  202. *
  203. * @param string $command
  204. * @param int $times
  205. * @return void
  206. */
  207. public function assertDispatchedAfterResponseTimes($command, $times = 1)
  208. {
  209. $count = $this->dispatchedAfterResponse($command)->count();
  210. PHPUnit::assertSame(
  211. $times, $count,
  212. "The expected [{$command}] job was pushed {$count} times instead of {$times} times."
  213. );
  214. }
  215. /**
  216. * Determine if a job was dispatched based on a truth-test callback.
  217. *
  218. * @param string|\Closure $command
  219. * @param callable|null $callback
  220. * @return void
  221. */
  222. public function assertNotDispatchedAfterResponse($command, $callback = null)
  223. {
  224. if ($command instanceof Closure) {
  225. [$command, $callback] = [$this->firstClosureParameterType($command), $command];
  226. }
  227. PHPUnit::assertCount(
  228. 0, $this->dispatchedAfterResponse($command, $callback),
  229. "The unexpected [{$command}] job was dispatched after sending the response."
  230. );
  231. }
  232. /**
  233. * Assert if a chain of jobs was dispatched.
  234. *
  235. * @param array $expectedChain
  236. * @return void
  237. */
  238. public function assertChained(array $expectedChain)
  239. {
  240. $command = $expectedChain[0];
  241. $expectedChain = array_slice($expectedChain, 1);
  242. $callback = null;
  243. if ($command instanceof Closure) {
  244. [$command, $callback] = [$this->firstClosureParameterType($command), $command];
  245. } elseif (! is_string($command)) {
  246. $instance = $command;
  247. $command = get_class($instance);
  248. $callback = function ($job) use ($instance) {
  249. return serialize($this->resetChainPropertiesToDefaults($job)) === serialize($instance);
  250. };
  251. }
  252. PHPUnit::assertTrue(
  253. $this->dispatched($command, $callback)->isNotEmpty(),
  254. "The expected [{$command}] job was not dispatched."
  255. );
  256. PHPUnit::assertTrue(
  257. collect($expectedChain)->isNotEmpty(),
  258. 'The expected chain can not be empty.'
  259. );
  260. $this->isChainOfObjects($expectedChain)
  261. ? $this->assertDispatchedWithChainOfObjects($command, $expectedChain, $callback)
  262. : $this->assertDispatchedWithChainOfClasses($command, $expectedChain, $callback);
  263. }
  264. /**
  265. * Reset the chain properties to their default values on the job.
  266. *
  267. * @param mixed $job
  268. * @return mixed
  269. */
  270. protected function resetChainPropertiesToDefaults($job)
  271. {
  272. return tap(clone $job, function ($job) {
  273. $job->chainConnection = null;
  274. $job->chainQueue = null;
  275. $job->chainCatchCallbacks = null;
  276. $job->chained = [];
  277. });
  278. }
  279. /**
  280. * Assert if a job was dispatched with an empty chain based on a truth-test callback.
  281. *
  282. * @param string|\Closure $command
  283. * @param callable|null $callback
  284. * @return void
  285. */
  286. public function assertDispatchedWithoutChain($command, $callback = null)
  287. {
  288. if ($command instanceof Closure) {
  289. [$command, $callback] = [$this->firstClosureParameterType($command), $command];
  290. }
  291. PHPUnit::assertTrue(
  292. $this->dispatched($command, $callback)->isNotEmpty(),
  293. "The expected [{$command}] job was not dispatched."
  294. );
  295. $this->assertDispatchedWithChainOfClasses($command, [], $callback);
  296. }
  297. /**
  298. * Assert if a job was dispatched with chained jobs based on a truth-test callback.
  299. *
  300. * @param string $command
  301. * @param array $expectedChain
  302. * @param callable|null $callback
  303. * @return void
  304. */
  305. protected function assertDispatchedWithChainOfObjects($command, $expectedChain, $callback)
  306. {
  307. $chain = collect($expectedChain)->map(function ($job) {
  308. return serialize($job);
  309. })->all();
  310. PHPUnit::assertTrue(
  311. $this->dispatched($command, $callback)->filter(function ($job) use ($chain) {
  312. return $job->chained == $chain;
  313. })->isNotEmpty(),
  314. 'The expected chain was not dispatched.'
  315. );
  316. }
  317. /**
  318. * Assert if a job was dispatched with chained jobs based on a truth-test callback.
  319. *
  320. * @param string $command
  321. * @param array $expectedChain
  322. * @param callable|null $callback
  323. * @return void
  324. */
  325. protected function assertDispatchedWithChainOfClasses($command, $expectedChain, $callback)
  326. {
  327. $matching = $this->dispatched($command, $callback)->map->chained->map(function ($chain) {
  328. return collect($chain)->map(function ($job) {
  329. return get_class(unserialize($job));
  330. });
  331. })->filter(function ($chain) use ($expectedChain) {
  332. return $chain->all() === $expectedChain;
  333. });
  334. PHPUnit::assertTrue(
  335. $matching->isNotEmpty(), 'The expected chain was not dispatched.'
  336. );
  337. }
  338. /**
  339. * Determine if the given chain is entirely composed of objects.
  340. *
  341. * @param array $chain
  342. * @return bool
  343. */
  344. protected function isChainOfObjects($chain)
  345. {
  346. return ! collect($chain)->contains(function ($job) {
  347. return ! is_object($job);
  348. });
  349. }
  350. /**
  351. * Assert if a batch was dispatched based on a truth-test callback.
  352. *
  353. * @param callable $callback
  354. * @return void
  355. */
  356. public function assertBatched(callable $callback)
  357. {
  358. PHPUnit::assertTrue(
  359. $this->batched($callback)->count() > 0,
  360. 'The expected batch was not dispatched.'
  361. );
  362. }
  363. /**
  364. * Assert the number of batches that have been dispatched.
  365. *
  366. * @param int $count
  367. * @return void
  368. */
  369. public function assertBatchCount($count)
  370. {
  371. PHPUnit::assertCount(
  372. $count, $this->batches,
  373. );
  374. }
  375. /**
  376. * Get all of the jobs matching a truth-test callback.
  377. *
  378. * @param string $command
  379. * @param callable|null $callback
  380. * @return \Illuminate\Support\Collection
  381. */
  382. public function dispatched($command, $callback = null)
  383. {
  384. if (! $this->hasDispatched($command)) {
  385. return collect();
  386. }
  387. $callback = $callback ?: function () {
  388. return true;
  389. };
  390. return collect($this->commands[$command])->filter(function ($command) use ($callback) {
  391. return $callback($command);
  392. });
  393. }
  394. /**
  395. * Get all of the jobs dispatched synchronously matching a truth-test callback.
  396. *
  397. * @param string $command
  398. * @param callable|null $callback
  399. * @return \Illuminate\Support\Collection
  400. */
  401. public function dispatchedSync(string $command, $callback = null)
  402. {
  403. if (! $this->hasDispatchedSync($command)) {
  404. return collect();
  405. }
  406. $callback = $callback ?: function () {
  407. return true;
  408. };
  409. return collect($this->commandsSync[$command])->filter(function ($command) use ($callback) {
  410. return $callback($command);
  411. });
  412. }
  413. /**
  414. * Get all of the jobs dispatched after the response was sent matching a truth-test callback.
  415. *
  416. * @param string $command
  417. * @param callable|null $callback
  418. * @return \Illuminate\Support\Collection
  419. */
  420. public function dispatchedAfterResponse(string $command, $callback = null)
  421. {
  422. if (! $this->hasDispatchedAfterResponse($command)) {
  423. return collect();
  424. }
  425. $callback = $callback ?: function () {
  426. return true;
  427. };
  428. return collect($this->commandsAfterResponse[$command])->filter(function ($command) use ($callback) {
  429. return $callback($command);
  430. });
  431. }
  432. /**
  433. * Get all of the pending batches matching a truth-test callback.
  434. *
  435. * @param callable $callback
  436. * @return \Illuminate\Support\Collection
  437. */
  438. public function batched(callable $callback)
  439. {
  440. if (empty($this->batches)) {
  441. return collect();
  442. }
  443. return collect($this->batches)->filter(function ($batch) use ($callback) {
  444. return $callback($batch);
  445. });
  446. }
  447. /**
  448. * Determine if there are any stored commands for a given class.
  449. *
  450. * @param string $command
  451. * @return bool
  452. */
  453. public function hasDispatched($command)
  454. {
  455. return isset($this->commands[$command]) && ! empty($this->commands[$command]);
  456. }
  457. /**
  458. * Determine if there are any stored commands for a given class.
  459. *
  460. * @param string $command
  461. * @return bool
  462. */
  463. public function hasDispatchedSync($command)
  464. {
  465. return isset($this->commandsSync[$command]) && ! empty($this->commandsSync[$command]);
  466. }
  467. /**
  468. * Determine if there are any stored commands for a given class.
  469. *
  470. * @param string $command
  471. * @return bool
  472. */
  473. public function hasDispatchedAfterResponse($command)
  474. {
  475. return isset($this->commandsAfterResponse[$command]) && ! empty($this->commandsAfterResponse[$command]);
  476. }
  477. /**
  478. * Dispatch a command to its appropriate handler.
  479. *
  480. * @param mixed $command
  481. * @return mixed
  482. */
  483. public function dispatch($command)
  484. {
  485. if ($this->shouldFakeJob($command)) {
  486. $this->commands[get_class($command)][] = $command;
  487. } else {
  488. return $this->dispatcher->dispatch($command);
  489. }
  490. }
  491. /**
  492. * Dispatch a command to its appropriate handler in the current process.
  493. *
  494. * Queueable jobs will be dispatched to the "sync" queue.
  495. *
  496. * @param mixed $command
  497. * @param mixed $handler
  498. * @return mixed
  499. */
  500. public function dispatchSync($command, $handler = null)
  501. {
  502. if ($this->shouldFakeJob($command)) {
  503. $this->commandsSync[get_class($command)][] = $command;
  504. } else {
  505. return $this->dispatcher->dispatchSync($command, $handler);
  506. }
  507. }
  508. /**
  509. * Dispatch a command to its appropriate handler in the current process.
  510. *
  511. * @param mixed $command
  512. * @param mixed $handler
  513. * @return mixed
  514. */
  515. public function dispatchNow($command, $handler = null)
  516. {
  517. if ($this->shouldFakeJob($command)) {
  518. $this->commands[get_class($command)][] = $command;
  519. } else {
  520. return $this->dispatcher->dispatchNow($command, $handler);
  521. }
  522. }
  523. /**
  524. * Dispatch a command to its appropriate handler behind a queue.
  525. *
  526. * @param mixed $command
  527. * @return mixed
  528. */
  529. public function dispatchToQueue($command)
  530. {
  531. if ($this->shouldFakeJob($command)) {
  532. $this->commands[get_class($command)][] = $command;
  533. } else {
  534. return $this->dispatcher->dispatchToQueue($command);
  535. }
  536. }
  537. /**
  538. * Dispatch a command to its appropriate handler.
  539. *
  540. * @param mixed $command
  541. * @return mixed
  542. */
  543. public function dispatchAfterResponse($command)
  544. {
  545. if ($this->shouldFakeJob($command)) {
  546. $this->commandsAfterResponse[get_class($command)][] = $command;
  547. } else {
  548. return $this->dispatcher->dispatch($command);
  549. }
  550. }
  551. /**
  552. * Create a new chain of queueable jobs.
  553. *
  554. * @param \Illuminate\Support\Collection|array $jobs
  555. * @return \Illuminate\Foundation\Bus\PendingChain
  556. */
  557. public function chain($jobs)
  558. {
  559. $jobs = Collection::wrap($jobs);
  560. return new PendingChainFake($this, $jobs->shift(), $jobs->toArray());
  561. }
  562. /**
  563. * Attempt to find the batch with the given ID.
  564. *
  565. * @param string $batchId
  566. * @return \Illuminate\Bus\Batch|null
  567. */
  568. public function findBatch(string $batchId)
  569. {
  570. //
  571. }
  572. /**
  573. * Create a new batch of queueable jobs.
  574. *
  575. * @param \Illuminate\Support\Collection|array $jobs
  576. * @return \Illuminate\Bus\PendingBatch
  577. */
  578. public function batch($jobs)
  579. {
  580. return new PendingBatchFake($this, Collection::wrap($jobs));
  581. }
  582. /**
  583. * Record the fake pending batch dispatch.
  584. *
  585. * @param \Illuminate\Bus\PendingBatch $pendingBatch
  586. * @return \Illuminate\Bus\Batch
  587. */
  588. public function recordPendingBatch(PendingBatch $pendingBatch)
  589. {
  590. $this->batches[] = $pendingBatch;
  591. return (new BatchRepositoryFake)->store($pendingBatch);
  592. }
  593. /**
  594. * Determine if a command should be faked or actually dispatched.
  595. *
  596. * @param mixed $command
  597. * @return bool
  598. */
  599. protected function shouldFakeJob($command)
  600. {
  601. if (empty($this->jobsToFake)) {
  602. return true;
  603. }
  604. return collect($this->jobsToFake)
  605. ->filter(function ($job) use ($command) {
  606. return $job instanceof Closure
  607. ? $job($command)
  608. : $job === get_class($command);
  609. })->isNotEmpty();
  610. }
  611. /**
  612. * Set the pipes commands should be piped through before dispatching.
  613. *
  614. * @param array $pipes
  615. * @return $this
  616. */
  617. public function pipeThrough(array $pipes)
  618. {
  619. $this->dispatcher->pipeThrough($pipes);
  620. return $this;
  621. }
  622. /**
  623. * Determine if the given command has a handler.
  624. *
  625. * @param mixed $command
  626. * @return bool
  627. */
  628. public function hasCommandHandler($command)
  629. {
  630. return $this->dispatcher->hasCommandHandler($command);
  631. }
  632. /**
  633. * Retrieve the handler for a command.
  634. *
  635. * @param mixed $command
  636. * @return mixed
  637. */
  638. public function getCommandHandler($command)
  639. {
  640. return $this->dispatcher->getCommandHandler($command);
  641. }
  642. /**
  643. * Map a command to a handler.
  644. *
  645. * @param array $map
  646. * @return $this
  647. */
  648. public function map(array $map)
  649. {
  650. $this->dispatcher->map($map);
  651. return $this;
  652. }
  653. }