statt sich nur auf isolierte Tests zu verlassen
Ein isolierter Observer-Test ruft die Observer-Klasse direkt auf und prueft nie, ob das Event ueberhaupt in events.xml korrekt verdrahtet ist. Ein end-to-end Test ueber den echten Event-Dispatch deckt genau diese Luecke ab.
Inhaltsverzeichnis
- 1. Die trueglerische Sicherheit isolierter Observer-Tests
- 2. Der isolierte Test als Basis: notwendig, aber nicht ausreichend
- 3. Den echten Event-Dispatch ueber den EventManager testen
- 4. Ein Test-Double per di.xml-Preference fuer den Integrationstest einhaengen
- 5. Wie ein end-to-end Test einen Tippfehler im Event-Namen aufdeckt
- 6. Mehrere Observer am selben Event gemeinsam end-to-end testen
- 7. Area-Scope der Event-Registrierung mit einbeziehen
- 8. Wann ein isolierter Test ausnahmsweise ausreicht
- 9. Eine Checkliste fuer vollstaendige Observer-Testabdeckung
- 10. Zusammenfassung
- 11. FAQ
1. Die trueglerische Sicherheit isolierter Observer-Tests
Ein klassischer Observer-Test instanziiert die Observer-Klasse direkt, uebergibt ein manuell zusammengebautes Observer-Objekt und prueft, ob die execute()-Methode das erwartete Verhalten zeigt. Das ist ein vollstaendig legitimer Unit-Test fuer die Business-Logik innerhalb des Observers, hat aber eine entscheidende Luecke: Er prueft zu keinem Zeitpunkt, ob das Event, auf das der Observer reagieren soll, tatsaechlich an ihn dispatcht wird.
Diese Luecke ist gefaehrlich, weil sie einen kompletten Ausfall der Funktionalitaet unsichtbar macht. Ein Tippfehler im Event-Namen in der events.xml, ein vergessener Eintrag im falschen Bereich (global statt frontend), oder ein Observer-Name, der mit einem anderen Modul kollidiert und dadurch stillschweigend deaktiviert wird, all das faellt bei einem isolierten Test nicht auf. Die Observer-Klasse selbst ist korrekt, wird aber nie aufgerufen, weil die Verdrahtung fehlt.
2. Der isolierte Test als Basis: notwendig, aber nicht ausreichend
Ein isolierter Test bleibt trotzdem sinnvoll und sollte nicht ersetzt, sondern ergaenzt werden. Er prueft schnell und praezise, ob die Business-Logik innerhalb der Observer-Klasse fuer verschiedene Eingabedaten korrekt reagiert, etwa ob ein Kunden-Willkommens-E-Mail-Observer bei einer Neuregistrierung tatsaechlich den richtigen E-Mail-Versand-Service mit den richtigen Parametern aufruft. Solche Tests laufen in Millisekunden und decken Randfaelle innerhalb der Logik gut ab.
Der isolierte Test allein reicht aber nicht aus, um Vertrauen in das Gesamtsystem zu schaffen. Er beantwortet die Frage 'Verhaelt sich der Observer korrekt, wenn er aufgerufen wird?', aber nicht die viel grundlegendere Frage 'Wird der Observer ueberhaupt aufgerufen?'. Genau diese zweite Frage beantwortet nur ein end-to-end Test ueber den echten Event-Dispatch.
<?php
declare(strict_types=1);
namespace Mironsoft\CustomerWelcome\Test\Unit\Observer;
use Magento\Customer\Api\Data\CustomerInterface;
use Magento\Framework\Event\Observer;
use Mironsoft\CustomerWelcome\Model\WelcomeMailSender;
use Mironsoft\CustomerWelcome\Observer\SendWelcomeMailObserver;
use PHPUnit\Framework\TestCase;
class SendWelcomeMailObserverTest extends TestCase
{
public function testExecuteSendsWelcomeMailToNewCustomer(): void
{
$customerMock = $this->createMock(CustomerInterface::class);
$customerMock->method('getEmail')->willReturn('customer@example.com');
$observerMock = $this->createMock(Observer::class);
$observerMock->method('getData')->with('customer')->willReturn($customerMock);
$mailSenderMock = $this->createMock(WelcomeMailSender::class);
$mailSenderMock->expects(self::once())
->method('send')
->with('customer@example.com');
$observer = new SendWelcomeMailObserver($mailSenderMock);
$observer->execute($observerMock);
}
}
3. Den echten Event-Dispatch ueber den EventManager testen
Fuer den end-to-end Test wird nicht die Observer-Klasse direkt aufgerufen, sondern Magento\Framework\Event\ManagerInterface::dispatch() mit demselben Event-Namen, der auch im echten Anwendungsfall ausgeloest wird, in diesem Fall customer_register_success. Dieser Aufruf durchlaeuft die komplette Magento-Event-Infrastruktur, liest die tatsaechliche events.xml-Konfiguration und ruft alle registrierten Observer in ihrer konfigurierten Reihenfolge auf.
Damit ein solcher Test etwas beweist, muss er ueber ein beobachtbares Ergebnis verfuegen, zum Beispiel, dass eine E-Mail tatsaechlich in Magentos Test-Mail-Transport landet, oder dass ein injizierter Test-Double des Mail-Senders tatsaechlich aufgerufen wurde. Ein Integrationstest mit dem echten Object Manager ist hierfuer der richtige Rahmen, weil nur er die vollstaendige, aus XML generierte Verdrahtung nutzt.
<?php
declare(strict_types=1);
namespace Mironsoft\CustomerWelcome\Test\Integration\Observer;
use Magento\Framework\Event\ManagerInterface;
use Magento\TestFramework\Helper\Bootstrap;
use Mironsoft\CustomerWelcome\Test\Fixture\RecordingWelcomeMailSender;
use PHPUnit\Framework\TestCase;
class SendWelcomeMailObserverIntegrationTest extends TestCase
{
public function testEventTriggersObserverThroughRealEventDispatch(): void
{
$objectManager = Bootstrap::getObjectManager();
/** @var RecordingWelcomeMailSender $recordingSender */
$recordingSender = $objectManager->get(RecordingWelcomeMailSender::class);
$recordingSender->reset();
/** @var ManagerInterface $eventManager */
$eventManager = $objectManager->get(ManagerInterface::class);
$customer = $objectManager->create(\Magento\Customer\Api\Data\CustomerInterface::class);
$customer->setEmail('new-customer@example.com');
$eventManager->dispatch('customer_register_success', ['customer' => $customer]);
self::assertSame(['new-customer@example.com'], $recordingSender->getSentEmails());
}
}
4. Ein Test-Double per di.xml-Preference fuer den Integrationstest einhaengen
Damit der end-to-end Test keine echten E-Mails verschickt, wird der reale WelcomeMailSender im Testkontext durch ein aufzeichnendes Test-Double ersetzt. Das geschieht ueber eine di.xml-Preference, die ausschliesslich im Integrationstest-Modul aktiv ist, typischerweise in dev/tests/integration/etc/di.xml oder einem aequivalenten, testspezifischen Konfigurationsbereich, sodass Produktionscode davon unberuehrt bleibt.
Dieses Test-Double implementiert dieselbe Schnittstelle wie der echte Mail-Sender, zeichnet aber lediglich auf, mit welchen Parametern es aufgerufen wurde, statt tatsaechlich eine E-Mail zu versenden. Der Vorteil gegenueber einem klassischen Mock ist, dass dieses Double durch den echten Object Manager injiziert wird und damit die komplette Dependency-Injection-Kette, inklusive Observer-Konstruktor, tatsaechlich durchlaeuft.
<?php
declare(strict_types=1);
namespace Mironsoft\CustomerWelcome\Test\Fixture;
use Mironsoft\CustomerWelcome\Model\WelcomeMailSender;
class RecordingWelcomeMailSender extends WelcomeMailSender
{
/** @var string[] */
private array $sentEmails = [];
public function send(string $email): void
{
$this->sentEmails[] = $email;
}
public function reset(): void
{
$this->sentEmails = [];
}
/**
* @return string[]
*/
public function getSentEmails(): array
{
return $this->sentEmails;
}
}
5. Wie ein end-to-end Test einen Tippfehler im Event-Namen aufdeckt
Der konkrete Nutzen wird sichtbar, sobald man sich vorstellt, dass in der events.xml versehentlich customer_register_succes statt customer_register_success steht, ein einzelnes fehlendes 's'. Der isolierte Observer-Test aus dem zweiten Abschnitt wuerde weiterhin problemlos gruen bleiben, weil er die Observer-Klasse direkt aufruft und die events.xml gar nicht liest.
Der end-to-end Test dagegen dispatcht bewusst das korrekte Event customer_register_success, so wie es auch der echte Registrierungs-Controller tut. Da der Observer wegen des Tippfehlers nicht registriert ist, bleibt getSentEmails() leer, und die Assertion schlaegt fehl. Dieser Fehlschlag zeigt exakt das Symptom, das auch ein echter Kunde erleben wuerde: Die Willkommens-E-Mail wird nicht versendet, obwohl der Code fuer sich genommen korrekt ist.
6. Mehrere Observer am selben Event gemeinsam end-to-end testen
In vielen Shops haengen mehrere Module am selben Event, etwa customer_register_success fuer den Willkommens-Mail-Observer, einen Newsletter-Anmelde-Observer und einen CRM-Synchronisations-Observer. Ein end-to-end Test kann in einem einzigen dispatch()-Aufruf pruefen, dass alle relevanten Observer tatsaechlich reagieren, statt fuer jeden Observer separat zu vertrauen, dass die jeweilige events.xml korrekt ist.
Dieser gemeinsame Test ist besonders wertvoll, um Seiteneffekte zwischen Observern aufzudecken, etwa wenn ein fruehzeitig ausgefuehrter Observer eine Exception wirft, die je nach Magento-Konfiguration die Ausfuehrung der nachfolgenden Observer am selben Event verhindert. Ein isolierter Test jedes einzelnen Observers wuerde ein solches Zusammenspiel-Problem grundsaetzlich nicht erkennen koennen, weil er die anderen Observer gar nicht kennt.
<?php
declare(strict_types=1);
namespace Mironsoft\CustomerWelcome\Test\Integration\Observer;
use Magento\Framework\Event\ManagerInterface;
use Magento\TestFramework\Helper\Bootstrap;
use Mironsoft\CustomerWelcome\Test\Fixture\RecordingWelcomeMailSender;
use Mironsoft\Newsletter\Test\Fixture\RecordingNewsletterSubscriber;
use PHPUnit\Framework\TestCase;
class CustomerRegisterSuccessAllObserversTest extends TestCase
{
public function testAllRegisteredObserversReactToTheEvent(): void
{
$objectManager = Bootstrap::getObjectManager();
/** @var RecordingWelcomeMailSender $mailSender */
$mailSender = $objectManager->get(RecordingWelcomeMailSender::class);
/** @var RecordingNewsletterSubscriber $newsletterSubscriber */
$newsletterSubscriber = $objectManager->get(RecordingNewsletterSubscriber::class);
$mailSender->reset();
$newsletterSubscriber->reset();
/** @var ManagerInterface $eventManager */
$eventManager = $objectManager->get(ManagerInterface::class);
$customer = $objectManager->create(\Magento\Customer\Api\Data\CustomerInterface::class);
$customer->setEmail('new-customer@example.com');
$eventManager->dispatch('customer_register_success', ['customer' => $customer]);
self::assertSame(['new-customer@example.com'], $mailSender->getSentEmails());
self::assertSame(['new-customer@example.com'], $newsletterSubscriber->getSubscribedEmails());
}
}
7. Area-Scope der Event-Registrierung mit einbeziehen
Ein haeufiger, aber leicht zu uebersehender Fehler ist ein Observer, der in der events.xml im falschen Bereich registriert wird, etwa im frontend-Bereich statt im global-Bereich, obwohl das Event sowohl im Frontend als auch aus der Admin-API heraus ausgeloest werden kann. Ein end-to-end Test, der nur im Frontend-Kontext laeuft, wuerde diesen Fehler nicht aufdecken, weil er zufaellig genau den Bereich testet, in dem der Observer registriert ist.
Fuer Events, die aus mehreren Areas ausgeloest werden koennen, lohnt es sich, den Dispatch-Test bewusst in dem Area-Kontext auszufuehren, der fuer den jeweiligen Use-Case tatsaechlich relevant ist, etwa einen zusaetzlichen Test fuer den Fall, dass ein Kunde ueber die REST-API statt ueber das Storefront-Formular registriert wird. Erst diese Kombination aus mehreren Area-Kontexten deckt eine falsch gewaehlte Area in der events.xml zuverlaessig auf.
8. Wann ein isolierter Test ausnahmsweise ausreicht
Nicht jeder Observer braucht zwingend einen end-to-end Test. Bei sehr einfachen, unkritischen Observern, etwa einem Logging-Observer, dessen Ausfall lediglich zu einem fehlenden Log-Eintrag fuehrt, aber keine fachliche Konsequenz hat, kann der Aufwand eines end-to-end Tests unverhaeltnismaessig sein. Hier reicht ein isolierter Test in Kombination mit einer manuellen Verifikation waehrend der Entwicklung.
Die Entscheidung sollte sich am Schaden orientieren, den eine falsche Verdrahtung anrichten wuerde. Bei Observern mit direkten Auswirkungen auf Umsatz, Kundenkommunikation oder Datenintegritaet, etwa Zahlungsbestaetigungen, Bestandsreduzierung oder eben Willkommens-E-Mails, ist ein end-to-end Test die richtige Investition. Bei rein kosmetischen oder optionalen Nebeneffekten kann der isolierte Test allein genuegen.
9. Eine Checkliste fuer vollstaendige Observer-Testabdeckung
Zusammengefasst ergibt sich eine zweistufige Teststrategie fuer jeden geschaeftskritischen Observer: Erstens ein isolierter Unit-Test, der die Business-Logik innerhalb der Observer-Klasse fuer verschiedene Eingaben und Randfaelle prueft, schnell und praezise. Zweitens ein end-to-end Integrationstest, der ueber den echten EventManager::dispatch() die tatsaechliche Verdrahtung in der events.xml verifiziert, inklusive Event-Name, Area-Scope und Zusammenspiel mit anderen Observern am selben Event.
Diese Kombination schliesst die Luecke, die ein rein isolierter Test systematisch offen laesst, ohne dabei auf die Geschwindigkeit und Praezision von Unit-Tests fuer die eigentliche Logik zu verzichten. Ein Team, das beide Testebenen konsequent fuer geschaeftskritische Observer einsetzt, entdeckt Verdrahtungsfehler beim naechsten Testlauf, nicht erst, wenn ein Kunde eine fehlende Bestaetigungs-E-Mail meldet.
| Testart | Was sie prueft | Was sie NICHT prueft | Werkzeug |
|---|---|---|---|
| Isolierter Unit-Test | Business-Logik innerhalb der Observer-Klasse | Ob das Event ueberhaupt an den Observer dispatcht wird | Direkte Instanziierung, gemockter Observer |
| End-to-end Dispatch-Test | Tatsaechliche Verdrahtung in events.xml, inklusive Event-Name und Area | Feingranulare Randfaelle der Business-Logik | EventManager::dispatch() ueber echten Object Manager |
| Mehrere-Observer-Test | Zusammenspiel mehrerer Observer am selben Event | Interne Logik jedes einzelnen Observers im Detail | Ein dispatch()-Aufruf, mehrere Recording-Doubles |
| Area-Scope-Test | Ob der Observer in allen relevanten Areas registriert ist | Business-Logik-Details | Dispatch-Test in unterschiedlichen Area-Kontexten |
Mironsoft
Testautomatisierung, Magento-Qualitätssicherung und CI-Integration
Tests, die echte Fehler finden statt nur grün zu leuchten?
Wir prüfen bestehende PHPUnit-Suiten auf Implementierungsdetail-Tests, flaky Tests und fehlende Coverage an kritischen Stellen und bauen daraus eine Teststrategie, die bei jedem Magento-Update wirklich Sicherheit gibt.
Test-Audit
Bestehende Suiten auf Mocking-Antipatterns und blinde Flecken prüfen.
Teststrategie
Unit-, Integrations- und MFTF-Tests sinnvoll für Magento-Projekte kombinieren.
CI-Integration
Schnelle, zuverlässige Testläufe in GitLab CI oder GitHub Actions einrichten.
10. Zusammenfassung
Observer-End-to-End-Tests: Das Wichtigste auf einen Blick
Kernidee
Isolierten Observer-Test durch einen end-to-end Test ueber echten EventManager::dispatch() ergaenzen.
Groesste Luecke ohne end-to-end
Ein Tippfehler oder ein fehlender Eintrag in events.xml bleibt bei rein isolierten Tests unsichtbar.
Werkzeug fuer den Integrationstest
Ein Recording-Test-Double per di.xml-Preference statt eines echten Mail- oder API-Aufrufs.
Wann nur isoliert reicht
Bei unkritischen Observern ohne direkte Umsatz-, Kommunikations- oder Datenauswirkung.