MailFake.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516
  1. <?php
  2. namespace Illuminate\Support\Testing\Fakes;
  3. use Closure;
  4. use Illuminate\Contracts\Mail\Factory;
  5. use Illuminate\Contracts\Mail\Mailable;
  6. use Illuminate\Contracts\Mail\Mailer;
  7. use Illuminate\Contracts\Mail\MailQueue;
  8. use Illuminate\Contracts\Queue\ShouldQueue;
  9. use Illuminate\Mail\MailManager;
  10. use Illuminate\Support\Traits\ForwardsCalls;
  11. use Illuminate\Support\Traits\ReflectsClosures;
  12. use PHPUnit\Framework\Assert as PHPUnit;
  13. class MailFake implements Factory, Fake, Mailer, MailQueue
  14. {
  15. use ForwardsCalls, ReflectsClosures;
  16. /**
  17. * The mailer instance.
  18. *
  19. * @var MailManager
  20. */
  21. public $manager;
  22. /**
  23. * The mailer currently being used to send a message.
  24. *
  25. * @var string
  26. */
  27. protected $currentMailer;
  28. /**
  29. * All of the mailables that have been sent.
  30. *
  31. * @var array
  32. */
  33. protected $mailables = [];
  34. /**
  35. * All of the mailables that have been queued.
  36. *
  37. * @var array
  38. */
  39. protected $queuedMailables = [];
  40. /**
  41. * Create a new mail fake.
  42. *
  43. * @param MailManager $manager
  44. * @return void
  45. */
  46. public function __construct(MailManager $manager)
  47. {
  48. $this->manager = $manager;
  49. }
  50. /**
  51. * Assert if a mailable was sent based on a truth-test callback.
  52. *
  53. * @param string|\Closure $mailable
  54. * @param callable|int|null $callback
  55. * @return void
  56. */
  57. public function assertSent($mailable, $callback = null)
  58. {
  59. [$mailable, $callback] = $this->prepareMailableAndCallback($mailable, $callback);
  60. if (is_numeric($callback)) {
  61. return $this->assertSentTimes($mailable, $callback);
  62. }
  63. $message = "The expected [{$mailable}] mailable was not sent.";
  64. if (count($this->queuedMailables) > 0) {
  65. $message .= ' Did you mean to use assertQueued() instead?';
  66. }
  67. PHPUnit::assertTrue(
  68. $this->sent($mailable, $callback)->count() > 0,
  69. $message
  70. );
  71. }
  72. /**
  73. * Assert if a mailable was sent a number of times.
  74. *
  75. * @param string $mailable
  76. * @param int $times
  77. * @return void
  78. */
  79. protected function assertSentTimes($mailable, $times = 1)
  80. {
  81. $count = $this->sent($mailable)->count();
  82. PHPUnit::assertSame(
  83. $times, $count,
  84. "The expected [{$mailable}] mailable was sent {$count} times instead of {$times} times."
  85. );
  86. }
  87. /**
  88. * Determine if a mailable was not sent or queued to be sent based on a truth-test callback.
  89. *
  90. * @param string|\Closure $mailable
  91. * @param callable|null $callback
  92. * @return void
  93. */
  94. public function assertNotOutgoing($mailable, $callback = null)
  95. {
  96. $this->assertNotSent($mailable, $callback);
  97. $this->assertNotQueued($mailable, $callback);
  98. }
  99. /**
  100. * Determine if a mailable was not sent based on a truth-test callback.
  101. *
  102. * @param string|\Closure $mailable
  103. * @param callable|null $callback
  104. * @return void
  105. */
  106. public function assertNotSent($mailable, $callback = null)
  107. {
  108. [$mailable, $callback] = $this->prepareMailableAndCallback($mailable, $callback);
  109. PHPUnit::assertCount(
  110. 0, $this->sent($mailable, $callback),
  111. "The unexpected [{$mailable}] mailable was sent."
  112. );
  113. }
  114. /**
  115. * Assert that no mailables were sent or queued to be sent.
  116. *
  117. * @return void
  118. */
  119. public function assertNothingOutgoing()
  120. {
  121. $this->assertNothingSent();
  122. $this->assertNothingQueued();
  123. }
  124. /**
  125. * Assert that no mailables were sent.
  126. *
  127. * @return void
  128. */
  129. public function assertNothingSent()
  130. {
  131. $mailableNames = collect($this->mailables)->map(
  132. fn ($mailable) => get_class($mailable)
  133. )->join(', ');
  134. PHPUnit::assertEmpty($this->mailables, 'The following mailables were sent unexpectedly: '.$mailableNames);
  135. }
  136. /**
  137. * Assert if a mailable was queued based on a truth-test callback.
  138. *
  139. * @param string|\Closure $mailable
  140. * @param callable|int|null $callback
  141. * @return void
  142. */
  143. public function assertQueued($mailable, $callback = null)
  144. {
  145. [$mailable, $callback] = $this->prepareMailableAndCallback($mailable, $callback);
  146. if (is_numeric($callback)) {
  147. return $this->assertQueuedTimes($mailable, $callback);
  148. }
  149. PHPUnit::assertTrue(
  150. $this->queued($mailable, $callback)->count() > 0,
  151. "The expected [{$mailable}] mailable was not queued."
  152. );
  153. }
  154. /**
  155. * Assert if a mailable was queued a number of times.
  156. *
  157. * @param string $mailable
  158. * @param int $times
  159. * @return void
  160. */
  161. protected function assertQueuedTimes($mailable, $times = 1)
  162. {
  163. $count = $this->queued($mailable)->count();
  164. PHPUnit::assertSame(
  165. $times, $count,
  166. "The expected [{$mailable}] mailable was queued {$count} times instead of {$times} times."
  167. );
  168. }
  169. /**
  170. * Determine if a mailable was not queued based on a truth-test callback.
  171. *
  172. * @param string|\Closure $mailable
  173. * @param callable|null $callback
  174. * @return void
  175. */
  176. public function assertNotQueued($mailable, $callback = null)
  177. {
  178. [$mailable, $callback] = $this->prepareMailableAndCallback($mailable, $callback);
  179. PHPUnit::assertCount(
  180. 0, $this->queued($mailable, $callback),
  181. "The unexpected [{$mailable}] mailable was queued."
  182. );
  183. }
  184. /**
  185. * Assert that no mailables were queued.
  186. *
  187. * @return void
  188. */
  189. public function assertNothingQueued()
  190. {
  191. $mailableNames = collect($this->queuedMailables)->map(
  192. fn ($mailable) => get_class($mailable)
  193. )->join(', ');
  194. PHPUnit::assertEmpty($this->queuedMailables, 'The following mailables were queued unexpectedly: '.$mailableNames);
  195. }
  196. /**
  197. * Assert the total number of mailables that were sent.
  198. *
  199. * @param int $count
  200. * @return void
  201. */
  202. public function assertSentCount($count)
  203. {
  204. $total = collect($this->mailables)->count();
  205. PHPUnit::assertSame(
  206. $count, $total,
  207. "The total number of mailables sent was {$total} instead of {$count}."
  208. );
  209. }
  210. /**
  211. * Assert the total number of mailables that were queued.
  212. *
  213. * @param int $count
  214. * @return void
  215. */
  216. public function assertQueuedCount($count)
  217. {
  218. $total = collect($this->queuedMailables)->count();
  219. PHPUnit::assertSame(
  220. $count, $total,
  221. "The total number of mailables queued was {$total} instead of {$count}."
  222. );
  223. }
  224. /**
  225. * Assert the total number of mailables that were sent or queued.
  226. *
  227. * @param int $count
  228. * @return void
  229. */
  230. public function assertOutgoingCount($count)
  231. {
  232. $total = collect($this->mailables)
  233. ->concat($this->queuedMailables)
  234. ->count();
  235. PHPUnit::assertSame(
  236. $count, $total,
  237. "The total number of outgoing mailables was {$total} instead of {$count}."
  238. );
  239. }
  240. /**
  241. * Get all of the mailables matching a truth-test callback.
  242. *
  243. * @param string|\Closure $mailable
  244. * @param callable|null $callback
  245. * @return \Illuminate\Support\Collection
  246. */
  247. public function sent($mailable, $callback = null)
  248. {
  249. [$mailable, $callback] = $this->prepareMailableAndCallback($mailable, $callback);
  250. if (! $this->hasSent($mailable)) {
  251. return collect();
  252. }
  253. $callback = $callback ?: fn () => true;
  254. return $this->mailablesOf($mailable)->filter(fn ($mailable) => $callback($mailable));
  255. }
  256. /**
  257. * Determine if the given mailable has been sent.
  258. *
  259. * @param string $mailable
  260. * @return bool
  261. */
  262. public function hasSent($mailable)
  263. {
  264. return $this->mailablesOf($mailable)->count() > 0;
  265. }
  266. /**
  267. * Get all of the queued mailables matching a truth-test callback.
  268. *
  269. * @param string|\Closure $mailable
  270. * @param callable|null $callback
  271. * @return \Illuminate\Support\Collection
  272. */
  273. public function queued($mailable, $callback = null)
  274. {
  275. [$mailable, $callback] = $this->prepareMailableAndCallback($mailable, $callback);
  276. if (! $this->hasQueued($mailable)) {
  277. return collect();
  278. }
  279. $callback = $callback ?: fn () => true;
  280. return $this->queuedMailablesOf($mailable)->filter(fn ($mailable) => $callback($mailable));
  281. }
  282. /**
  283. * Determine if the given mailable has been queued.
  284. *
  285. * @param string $mailable
  286. * @return bool
  287. */
  288. public function hasQueued($mailable)
  289. {
  290. return $this->queuedMailablesOf($mailable)->count() > 0;
  291. }
  292. /**
  293. * Get all of the mailed mailables for a given type.
  294. *
  295. * @param string $type
  296. * @return \Illuminate\Support\Collection
  297. */
  298. protected function mailablesOf($type)
  299. {
  300. return collect($this->mailables)->filter(fn ($mailable) => $mailable instanceof $type);
  301. }
  302. /**
  303. * Get all of the mailed mailables for a given type.
  304. *
  305. * @param string $type
  306. * @return \Illuminate\Support\Collection
  307. */
  308. protected function queuedMailablesOf($type)
  309. {
  310. return collect($this->queuedMailables)->filter(fn ($mailable) => $mailable instanceof $type);
  311. }
  312. /**
  313. * Get a mailer instance by name.
  314. *
  315. * @param string|null $name
  316. * @return \Illuminate\Contracts\Mail\Mailer
  317. */
  318. public function mailer($name = null)
  319. {
  320. $this->currentMailer = $name;
  321. return $this;
  322. }
  323. /**
  324. * Begin the process of mailing a mailable class instance.
  325. *
  326. * @param mixed $users
  327. * @return \Illuminate\Mail\PendingMail
  328. */
  329. public function to($users)
  330. {
  331. return (new PendingMailFake($this))->to($users);
  332. }
  333. /**
  334. * Begin the process of mailing a mailable class instance.
  335. *
  336. * @param mixed $users
  337. * @return \Illuminate\Mail\PendingMail
  338. */
  339. public function cc($users)
  340. {
  341. return (new PendingMailFake($this))->cc($users);
  342. }
  343. /**
  344. * Begin the process of mailing a mailable class instance.
  345. *
  346. * @param mixed $users
  347. * @return \Illuminate\Mail\PendingMail
  348. */
  349. public function bcc($users)
  350. {
  351. return (new PendingMailFake($this))->bcc($users);
  352. }
  353. /**
  354. * Send a new message with only a raw text part.
  355. *
  356. * @param string $text
  357. * @param \Closure|string $callback
  358. * @return void
  359. */
  360. public function raw($text, $callback)
  361. {
  362. //
  363. }
  364. /**
  365. * Send a new message using a view.
  366. *
  367. * @param \Illuminate\Contracts\Mail\Mailable|string|array $view
  368. * @param array $data
  369. * @param \Closure|string|null $callback
  370. * @return void
  371. */
  372. public function send($view, array $data = [], $callback = null)
  373. {
  374. if (! $view instanceof Mailable) {
  375. return;
  376. }
  377. $view->mailer($this->currentMailer);
  378. if ($view instanceof ShouldQueue) {
  379. return $this->queue($view, $data);
  380. }
  381. $this->currentMailer = null;
  382. $this->mailables[] = $view;
  383. }
  384. /**
  385. * Queue a new e-mail message for sending.
  386. *
  387. * @param \Illuminate\Contracts\Mail\Mailable|string|array $view
  388. * @param string|null $queue
  389. * @return mixed
  390. */
  391. public function queue($view, $queue = null)
  392. {
  393. if (! $view instanceof Mailable) {
  394. return;
  395. }
  396. $view->mailer($this->currentMailer);
  397. $this->currentMailer = null;
  398. $this->queuedMailables[] = $view;
  399. }
  400. /**
  401. * Queue a new e-mail message for sending after (n) seconds.
  402. *
  403. * @param \DateTimeInterface|\DateInterval|int $delay
  404. * @param \Illuminate\Contracts\Mail\Mailable|string|array $view
  405. * @param string|null $queue
  406. * @return mixed
  407. */
  408. public function later($delay, $view, $queue = null)
  409. {
  410. $this->queue($view, $queue);
  411. }
  412. /**
  413. * Infer mailable class using reflection if a typehinted closure is passed to assertion.
  414. *
  415. * @param string|\Closure $mailable
  416. * @param callable|null $callback
  417. * @return array
  418. */
  419. protected function prepareMailableAndCallback($mailable, $callback)
  420. {
  421. if ($mailable instanceof Closure) {
  422. return [$this->firstClosureParameterType($mailable), $mailable];
  423. }
  424. return [$mailable, $callback];
  425. }
  426. /**
  427. * Forget all of the resolved mailer instances.
  428. *
  429. * @return $this
  430. */
  431. public function forgetMailers()
  432. {
  433. $this->currentMailer = null;
  434. return $this;
  435. }
  436. /**
  437. * Handle dynamic method calls to the mailer.
  438. *
  439. * @param string $method
  440. * @param array $parameters
  441. * @return mixed
  442. */
  443. public function __call($method, $parameters)
  444. {
  445. return $this->forwardCallTo($this->manager, $method, $parameters);
  446. }
  447. }