Ein Unit-Test zum Testen, ob E-Mails innerhalb unserer Anwendung versendet werden können
Lange Rede, kurzer Sinn: Ich bin nicht sehr an Unit-Tests gewöhnt, und ich wollte etwas Neues lernen. Ich beschloss, einen Unit-Test zu schreiben, um sicherzustellen, dass ich von meiner CodeIgniter 4-Anwendung aus E-Mails versenden kann.
Nun, es stellte sich heraus, dass der E-Mail-Dienst von CodeIgniter 4 standardmäßig eine Mocked Class verwendet, um dies zu tun, und daher gab mein Test immer true für die send() -Methode der E-Mail-Klasse zurück .
Inhaltsübersicht
Schreiben des Tests
Ich beginne mit dem Test, den ich durchführen möchte:
$email = service('email'); $email->setFrom(env('email.from'), env('email.fromName')); $email->setTo(env('email.testRecipient')); $email->setSubject('Email Test'); $email->setMessage('Testing that the email can be sent properly.'); $this->assertTrue($email->send()); // Warum gibt dies immer true zurück?
Im Allgemeinen scheint die Idee gut zu sein. Aber es gab ein Problem. Mein Test gab immer true zurück, auch wenn die E-Mail nicht gesendet wurde.
Warum gibt der Test true zurück?
Da ich noch kein Experte für Unit-Tests bin, dachte ich, ich würde etwas falsch machen. Natürlich war das erwartete Ergebnis das Bestehen des Tests, aber die E-Mail wurde nicht wirklich versendet. Außerdem habe ich noch keine Erfahrung mit CodeIgniter 4. Also beschloss ich, dem Problem auf den Grund zu gehen, und ich fand sozusagen den "Schuldigen".
Standardmäßig führt CodeIgniter Tests unter Verwendung einiger vordefinierter Mock-Klassen durch, d.h. einer Reihe von Fake-Klassen, die eigentlich keine endgültigen Aktionen durchführen sollten.
In der Dokumentation heißt es, dass dies absichtlich so gemacht wird, um aufdringliches Testverhalten zu verhindern (siehe hier: Testen).
Mein Test hat immer true zurückgegeben, weil er die send()-Methode der nachgebildeten Klasse und nicht der echten Email-Klasse verwendet hat.
MockEmail-Klasse
Die MockEmail-Klasse sieht wie folgt aus:
class MockEmail extends Email { /** * Wert, der von der nachgebildeten send() zurückgegeben wird.
* * @var bool */ public $returnValue = true; public function send($autoClear = true) { if ($this->returnValue) { $this->setArchiveValues(); if ($autoClear) { $this->clear(); } Events::trigger('email', $this->archive); } return $this->returnValue; } }
Wie Sie sehen können, wird die E-Mail mit der Methode send() nicht wirklich gesendet. Zumindest musste ich eine SMTP-Nachricht senden, und diese Methode tut das nicht.
Wahrscheinlich könnte man die MockEmail-Klasse einfach überschreiben, aber das schien mir kein echtes Szenario zu sein. Ich meine, ich möchte testen, dass die Anwendung tatsächlich E-Mails an meine Benutzer senden kann, also muss ich denselben Weg einschlagen, sonst würde der Test nicht viel Sinn machen, oder?
Wenn ich testen muss, dass ich die E-Mail empfangen kann, möchte ich kein mock send(), richtig?
Überschreiben der setUpMethods der Testklasse
In den Docs steht auch, dass man 'mockEmail' aus der Test Klasse entfernen kann, innerhalb der $setUpMethods.
Im Code für $setUpMethods gibt es diesen warnenden Kommentar:
// CodeIgniter\Test\CIUnitTestCase.php /** * Während des SetUp auszuführende Methoden * * WARNUNG: Nicht überschreiben, es sei denn, Sie wissen genau, was Sie tun. * * Diese Eigenschaft kann in Zukunft veraltet sein. * * @var array of methods */ protected $setUpMethods = [ 'resetFactories', 'mockCache', 'mockEmail', 'mockSession', ];
Von dieser Lösung scheint also abzuraten zu sein. Aber was könnte ein "aufdringliches Verhalten" in der Testphase sein? Ich bin mir nicht sicher. Vielleicht könnte es ein Problem geben, wenn man Tests mit öffentlichen Aufrufen (get, post) zur Verfügung stellt. Dies ist aber nicht mein Fall.
Lösung und funktionierender Test
Meine Lösung ist die Umgehung der Mocked-Klasse, wie folgt.
<?php // tests\app\Emails\SendEmailTest.php namespace App\Emails; use CodeIgniter\Test\CIUnitTestCase; final class SendEmailTest extends CIUnitTestCase { private $email = null; public function __construct() { parent::__construct(); // Entfernen der mockEmail aus diesem Test $k = array_keys($this->setUpMethods, 'mockEmail')[0]; unset($this->setUpMethods[$k]); } public static function setUpBeforeClass(): void { parent::setUpBeforeClass(); return; } protected function setUp(): void { parent::setUp(); $this->email = service('email'); $this->email->setFrom(env('email.from'), env('email.fromName')); $this->email->setTo(env('email.testRecipient')); return; } final public function testCanSendEmails() { $this->email->setSubject('Email Test'); $this->email->setMessage('Testing that the email can be sent properly.'); $result = $this->email->send(); if (!$result) { d($this->email->printDebugger()); } $this->assertTrue($result); } }
Schlussfolgerungen
Die Frage ist: Liege ich richtig mit der Annahme, dass dies kein logisches Verhalten ist und verbessert werden sollte, oder gibt es einen guten Grund dafür, den ich nicht kenne? Gibt es eine bessere Möglichkeit, dies zu erreichen?
Lassen Sie mich Ihre Meinung in den Kommentaren unten wissen! Wenn Ihnen dieser Artikel gefallen hat, folgen Sie mir bitte auf Facebook und Youtube!