MailFake.php 16 KB

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