V rámci spuštěného testu nedochází k inicializaci předepsané v NEON souboru

DefenestrationPraha
Člen | 99
+
0
-

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
+
0
-

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 | 1162
+
+2
-

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 | 99
+
0
-

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 | 99
+
0
-

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 | 99
+
0
-

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 | 1162
+
+1
-

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 | 99
+
0
-

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 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

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.