V rámci spuštěného testu nedochází k inicializaci předepsané v NEON souboru
- DefenestrationPraha
- Člen | 120
Mám test, který jen kontroluje, zda jsou na stránce „About“ vyplněny hodnoty o současné verzi aplikace. Ty se do příslušného prezentéru (SystemPresenter) vkládají v rámci souboru version.neon:
parameters:
version_major: 0
version_minor: 2
version_build: 124
decorator:
App\Module\Admin\Presenters\SystemPresenter:
setup:
- injectVersionMajor(%version_major%)
- injectVersionMinor(%version_minor%)
- injectVersionBuild(%version_build%)
Pokud mám normálně spuštěnou webovou aplikaci v browseru, hladce to funguje. Ale jakmile udělám v rámci testu konstrukci prezentéru
$presenter = $this->createPresenter(Cn::ADMIN_MODULE_NAME, Cn::SYSTEM_PRESENTER_NAME);
$request = $this->createApplicationRequestA(Cn::ADMIN_MODULE_NAME, Cn::SYSTEM_PRESENTER_NAME, Cn::HTTP_METHOD_GET, array(KnCn::ACTION_NAME => Cn::ACTION_ABOUT));
$presenter->run($request);
výsledkem je hláška
ERROR: Typed property App\Module\Admin\Presenters\SystemPresenter::$versionMajor must not be accessed before initialization
a zároveň se mohu snadno přesvědčit umístěním breakpointu do metody injectVersionMajor(), že k předepsané inicializaci nedojde.
Co se děje? Vypadá to, jako by se soubor version.neon vůbec nenatahoval, přitom ale breakpoint v rámci Bootstrap.php na řádku
$configurator->addConfig($appDir . '/config/version.neon');
ukazuje, že na začátku testu se tento řádek vykoná. Správně by tedy mělo být nataženo.
- Petr Parolek
- Člen | 455
Ahoj, zkus použít na testy Mango tester https://github.com/…mango-tester , který se mi nejvíc osvědčil.
- Marek Bartoš
- Nette Blogger | 1239
Co dělá $this->createPresenter()
? Presenter by měl být
správně registrovaný jako služba a získán z DIC. Jestliže ho
konstruuješ jinak, tak ti samozřejmě DI konfigurace nebude fungovat.
Zkus spíš něco jako
$container->getByType(SystemPresenter::class);
- DefenestrationPraha
- Člen | 120
Marek Bartoš napsal(a):
Co dělá
$this->createPresenter()
? Presenter by měl být správně registrovaný jako služba a získán z DIC. Jestliže ho konstruuješ jinak, tak ti samozřejmě DI konfigurace nebude fungovat.Zkus spíš něco jako
$container->getByType(SystemPresenter::class);
$this->container = $configurator->createContainer();
$this->presenterFactory = $this->container->getByType('Nette\Application\IPresenterFactory');
$presenterInstance = $this->presenterFactory->createPresenter( $presenterName );
To, co je trochu divné, je, že u dalších 28 testů to funguje. Tam se ale nevyskytuje inicializace pomocí skalárů, tam ten autowiring vždycky zachází s objekty.
Vyzkouším tvůj tip.
- DefenestrationPraha
- Člen | 120
Marek Bartoš napsal(a):
Co dělá
$this->createPresenter()
? Presenter by měl být správně registrovaný jako služba a získán z DIC. Jestliže ho konstruuješ jinak, tak ti samozřejmě DI konfigurace nebude fungovat.Zkus spíš něco jako
$container->getByType(SystemPresenter::class);
Hodí mi to hlášku
ERROR: Service of type App\Module\Basic\Presenters\LoginPresenter is not autowired or is missing in di › export › types.
která pochází z Container.php, z těchto míst:
$methodType = (new \ReflectionMethod(static::class, $method))->getReturnType()->getName();
if (is_a($methodType, $type, true)) {
throw new MissingServiceException(sprintf(
"Service of type %s is not autowired or is missing in di\u{a0}›\u{a0}export\u{a0}›\u{a0}types.",
$type
));
Přitom ve struktuře wiring[] plně kvalifikované jméno toho prezentéru vidím, včetně vazby na nějakou tu create metodu. Všechny prezentéry tam vidím…
Ta konstrukce, jak jsem ji měl, funguje u dalších 28 testů. Nefunguje jen u toho jediného testu, který vyžaduje injektování skalárních hodnot do prezentéru. Jakmile jsou ty prezentéry závislé jen na instancích nějakých tříd, fungují skvěle.
- DefenestrationPraha
- Člen | 120
Když změním způsob injektáže na inject*() metody, začnu dostávat v Nette Testeru:
Nette\DI\ServiceCreationException: Service 'application.5' (type of App\Module\Admin\Presenters\SystemPresenter): Parameter $param in SystemPresenter::injectVersionMajor() has no class type or default value, so its value must be specified.
u mnoha dalších testů.
Zatím mi to připadá, jako bych narazil na zajímavý corner case v případě Nette Testeru a potřeby inicializace skalárními hodnotami.
Třeba by to šlo reprodukovat i u někoho jiného. Napsat si elementární Presenter, který má jednu proměnnou typu int, zkusit ji nainjektovat v rámci Nette Testeru, sledovat, jestli to udělá chybu.
- Marek Bartoš
- Nette Blogger | 1239
Service of type App\Module\Basic\Presenters\LoginPresenter is not autowired
Hmm, ona ApplicationExtension u presenterů vlastně vypíná autowiring.
Fungoval by $container->getByName()
, ale to by sis musel
presentery registrovat sám a pojmenovávat.
$this->presenterFactory->createPresenter( $presenterName );
Na PresenterFactory jsem zapomněl, s tou by ti to mělo v testech fungovat stejně jako v aplikaci.
Zkus smazat vygenerované DI kontejnery, možná se ti jen nepřegeneroval při změnách. Před testy je ideální containery automaticky smazat, abys měl jistotu, že se tvé změny v kódu ihned projeví.
Svůj DI container si vždy můžeš ověřit dumpnutím
get_class($container)
a nalezením souboru s třídou kterou ti to
vrací. Na začátku souboru najdeš seznam includnutých konfiguračních
souborů a ve třídě metodu vytvářející tvůj presenter
Zatím mi to připadá, jako bych narazil na zajímavý corner case v případě Nette Testeru
Testerem to nebude. Testy jen spouští a o tvém kódu nic neví. Spíš jen v testech děláš něco jinak než když kód spouštíš sám
Editoval Marek Bartoš (8. 8. 2022 13:01)
- DefenestrationPraha
- Člen | 120
Marek Bartoš napsal(a):
Service of type App\Module\Basic\Presenters\LoginPresenter is not autowired
Hmm, ona ApplicationExtension u presenterů vlastně vypíná autowiring. Fungoval by
$container->getByName()
, ale to by sis musel presentery registrovat sám a pojmenovávat.
$this->presenterFactory->createPresenter( $presenterName );
Na PresenterFactory jsem zapomněl, s tou by ti to mělo v testech fungovat stejně jako v aplikaci.
Zkus smazat vygenerované DI kontejnery, možná se ti jen nepřegeneroval při změnách. Před testy je ideální containery automaticky smazat, abys měl jistotu, že se tvé změny v kódu ihned projeví.
Svůj DI container si vždy můžeš ověřit dumpnutím
get_class($container)
a nalezením souboru s třídou kterou ti to vrací. Na začátku souboru najdeš seznam includnutých konfiguračních souborů a ve třídě metodu vytvářející tvůj presenterZatím mi to připadá, jako bych narazil na zajímavý corner case v případě Nette Testeru
Testerem to nebude. Testy jen spouští a o tvém kódu nic neví. Spíš jen v testech děláš něco jinak než když kód spouštíš sám
Měl jsi pravdu. Konfigurace probíhala jinak.
Zatímco v produkčním prostředí se volalo (správně)
Bootstrap::boot()->createContainer();
v testeru se spouštělo
$configurator = new Configurator();
$configurator->setTimeZone('Europe/Prague');
$configurator->setTempDirectory(KrakenTestConstants::TMP_DIR);
$configurator->createRobotLoader()
->addDirectory(KrakenTestConstants::APP_DIR)
->register();
$configurator->addParameters([
'appDir' => KrakenTestConstants::APP_DIR,
]);
foreach (KrakenTestConstants::CONFIG_FILES as $config) {
$configurator->addConfig(KrakenTestConstants::CONFIG_DIR.$config);
}
$this->container = $configurator->createContainer();
No, a ten seznam konfiguračních souborů nebyl stejný. Bingo.
Děkuji za velkou pomoc s vyhmátnutím problému.
Zároveň srdečně doporučuji všem, napsat si co nejvíce testů, a to včetně těch zdánlivě banálních (např. kontrola, že stránka „O aplikaci“ ukazuje, co má). Nikdy nevíte, jaké subtilní problémy přitom odhalíte.