Nette\Tester: testování metod po přihlášení
- zimmi
- Člen | 94
Zdravím,
pročetl jsem si vlákna týkající se testování přihlašování uživatelů
a používání metod po přihlášení, ale nejsem z toho moc moudrý, proto
mám několik otázek.
class UserPresenterTest extends BaseTestCase
{
public function testLogIn()
{
$presenter = $this->getPresenter('Admin:Login');
$request = new Nette\Application\Request('Admin:Login', 'POST', array(
'action' => 'default',
'do' => 'loginForm-submit',
), array(
'username' => 'user',
'password' => 'pass',
));
$this->getContainer()->user->logout();
$response = $presenter->run($request);
Assert::true( $this->getContainer()->user->isLoggedIn() );
}
}
id(new UserPresenterTest($container))->run();
Snažím se přihlásit pomocí tohoto kódu, výsledkem assertu je ale
False. Nic víc na výpisu konzole nedostávám, lze Nette\Tester donutit
k nějakému obsáhlejšímu výstupu?
Authenticator sahá při přihlašování do db, kde ověřuje existenci
uživatele pomocí modelu userRepo, který dostane v konstruktoru. V testu mu
tuto závislost musím nějak předat, nebo se předá tak jako v aplikaci?
Poprvé píšu testy, budu tedy rád za jakékoliv popostrčení.
- zimmi
- Člen | 94
besanek napsal(a):
Já osobně řeším login tak, že si vytvořím identitu. Zhruba takto.
$admin = new Nette\Security\Identity(1, 'admin', array('nick' => 'admin')); $user = $this->getContainer()->getByType('Nette\Security\User'); $user->login($admin);
Můžeš pak nějak otestovat, jestli ti přihlašovací formulář funguje tak, jak by měl fungovat?
- besanek
- Člen | 128
Ah sorry. Špatně jsem to pochopil. Myslel jsem, že chceš testovat něco, k čemu musíš být přihlášený.
Takže znovu a lépe.
Všechno se předá stejně jako v aplikaci. Dumpni si
$response
, jestli presenter vrací to co by měl. Tj.
RedirectResponse
. Pokud ti vrací TextResponse
, tak si
vypiš getSource()
a zkontroluj, jestli v něm není náhodou
vypsána nějaká chybová hláška.
- zimmi
- Člen | 94
No, asi jsem se vyjádřil špatně. Chtěl bych testovat jednak funkčnost přihlašovacího formuláře, dvak funkčnost řízení přístupu k jednotlivým metodám. Příklad: metodu renderXy() budu mít označenou jako Secured a přístup k ní bude mít uživatel v roli admin. V aplikaci se o přidělení přístupu stará Authorizator na základě databáze. V testu budu chtít otestovat:
- přístup k metodě renderXy() jako guest → chci obdržet RedirectResponse, protože k metodě nemám oprávnění. S tímhle testem nemám problém a vím, jak na něj.
- přístup k metodě renderXy() jako přihlášený uživatel → tzn. chtěl bych ten presenter zavolat tak, aby mě přijal jako přihlášeného uživatele.
besanek napsal(a):
Ah sorry. Špatně jsem to pochopil. Myslel jsem, že chceš testovat něco, k čemu musíš být přihlášený.
Takže znovu a lépe.
Všechno se předá stejně jako v aplikaci. Dumpni si
$response
, jestli presenter vrací to co by měl. Tj.RedirectResponse
. Pokud ti vracíTextResponse
, tak si vypišgetSource()
a zkontroluj, jestli v něm není náhodou vypsána nějaká chybová hláška.
- enumag
- Člen | 2118
@zimmi: V zásadě znám dvě možnosti:
- Funkcionální (integrační) test:
// create $container
// přihlaš uživatele
$presenter = $container->getByType('Nette\Application\IPresenterFactory')->createPresenter($presenterName);
$request = new \Nette\Application\Request($presenterName, ...); //parametry podle toho co testuješ
$response = $presenter->run($request);
//to celé zabalit do funkce která by měla vyhodit určitou výjimku - tu zachytíš a otestuješ že je taková jakou jsi čekal (např. nějaká ForbiddenRequestException)
//anebo otestuješ $response pokud výjimka neměla nastat
- Akceptační test: Selenium.
Editoval enumag (31. 7. 2013 20:32)
- zimmi
- Člen | 94
@enumag Mohl bys prosím rozvést to //přihlaš uživatele? Respektive existuje i jiný způsob než od besanek?
enumag napsal(a):
@zimmi: V zásadě znám dvě možnosti:
- Funkcionální (integrační) test:
// create $container // přihlaš uživatele $presenter = $container->getByType('Nette\Application\IPresenterFactory')->createPresenter($presenterName); $request = new \Nette\Application\Request($presenterName, ...); //parametry podle toho co testuješ $response = $presenter->run($request); //to celé zabalit do funkce která by měla vyhodit určitou výjimku - tu zachytíš a otestuješ že je taková jakou jsi čekal (např. nějaká ForbiddenRequestException) //anebo otestuješ $response pokud výjimka neměla nastat
- Akceptační test: Selenium.
Editoval zimmi (31. 7. 2013 20:53)
- enumag
- Člen | 2118
@zimni:
Pro funkcionální test akce přihlášení (přesněji signálu submit na login formuláři) vytvoříš request s danou akcí a signálem, pošleš ho presenteru jak jsem napsal a na konci ověříš že uživatel je přihlášen takhle:
assertTrue($container->getByType('Nette\Security\User')->isLoggedIn());
//+ případně ověřit identitu uživatele
EDIT: Nečetl jsem téma od začátku, teď koukám že přesně to jsi udělal. V tom případě bude asi nějaký problém s vytvářením DI containeru. Respektive zkus si nejdříve samostatně otestovat authenticator.
Pro funkcionální test nějaké akce po přihlášení je způsob od @besanek naprosto v pořádku.
Můžeš samozřejmě dělat obojí v jednom testu, ale připadá mi lepší mít ty testy oddělené.
Editoval enumag (31. 7. 2013 21:07)
- zimmi
- Člen | 94
@enumag:
Vrací
Nette\DI\MissingServiceException: Service of type Nette\Security\Authenticator not found. in Nette/DI/Container.php(208) in gismob/tests/UserPresenterTest.php(97) Nette\DI\Container->getByType()
a container:
<?php
require __DIR__ . '/../libs/autoload.php';
if (!class_exists('Tester\Assert')) {
echo "Install Nette Tester using `composer update --dev`\n";
exit(1);
}
Tester\Helpers::setup();
function id($val) {
return $val;
}
$configurator = new Nette\Config\Configurator;
$configurator->setDebugMode(True);
$configurator->setTempDirectory(__DIR__ . '/../temp');
$configurator->createRobotLoader()
->addDirectory(__DIR__ . '/../app')
->register();
$configurator->addConfig(__DIR__ . '/../app/config/config.neon');
$configurator->addConfig(__DIR__ . '/../app/config/config.local.neon', $configurator::NONE); // none section
$container = $configurator->createContainer();
return $container;
Preventivně přikládám i část dumpu $containeru (relevatní):
"nette\database\connection" => "nette.database.default" (22)
"adminmodule\registrator" => "registrator" (11)
routerfactory => "routerFactory" (13)
"adminmodule\repository" => "groupRepository" (15)
"adminmodule\grouprepository" => "groupRepository" (15)
"adminmodule\rolerepository" => "roleRepository" (14)
"nette\security\iauthenticator" => "authenticator" (13)
"adminmodule\authenticator" => "authenticator" (13)
"nette\security\iauthorizator" => "authorizator" (12)
"adminmodule\authorizator" => "authorizator" (12)
"adminmodule\userrepository" => "userRepository" (14)
- zimmi
- Člen | 94
Teď to vrátí správnou hodnotu.
Nette\Security\Identity(4) {
id private => "51cbea41178ff375847627" (22)
roles private => array(1) [
0 => "51cbe8e59134c891901827" (22)
]
data private => array(0)
frozen private => FALSE
}
Kdybych chtěl nastavit pro $container->user authenticator na AdminModule\Authenticator, který má v konstruktoru AdminModule\UserRepo, který má v konstruktoru Nette\Database\Connection, jde to udělat nějak elegantně?
Editoval zimmi (31. 7. 2013 21:51)
- zimmi
- Člen | 94
@enumag :D
Tak se dívám, že $response je:
Nette\Application\Responses\RedirectResponse(2) {
url private => "http://localhost/admin/admin.login/default" (38)
code private => 302
}
Zdá se, že mi nefungují routy, mám nahozený virtualhost na www.domena.cz, takže url by mělo být http//domena.cz/admin/admin.login/default.
- zimmi
- Člen | 94
Tak já jsem z toho jelen.
public function testDefault()
{
$presenter = $this->getPresenter('Admin:Login');
$request = new \Nette\Application\Request('Admin:Login', 'GET', array(
'action' => 'default',
));
$response = $presenter->run($request);
// zkontroluji response
Assert::true( $response instanceof Nette\Application\Responses\TextResponse );
}
Nezabezpečený presenter funguje, jak má. Ale redirect těch zabezpečených mě přesměruje s kódem 302 na neexistující stránku (viz předchozí post). To bohužel nevím, jak vyřešit. Zkoušel jsem do bootstrapu přidat definici rout z z bootstrapu aplikace, což ovšem nepomohlo.
Editoval zimmi (1. 8. 2013 8:23)
- enumag
- Člen | 2118
@zimni: Imho bys měl použít Nette\Http\Url
a testovat jen $url->getRelativeUrl()
.
- Milo
- Nette Core | 1283
@zimni: Četl jsi Davidovo velestručné testování prezenterů?
K Testeru… Tester vypisuje hezký stack trace, kde k chybné aserci
došlo. Jen nesmíš zapomenout v každém testu zavolat
Tester\Environment::setup()
(resp.
Tester\Helpers::setup()
ve staší verzi). Testy můžeš
spouštět i samostatně jako obyčejný PHP skript. To se dobře hodí pro
ladění.
php tests/my-test.phpt
- zimmi
- Člen | 94
Četl. Zkouším to podle něj:
$presenterFactory = $container->getByType('Nette\Application\IPresenterFactory');
$presenter = $presenterFactory->createPresenter('Admin:Login');
$presenter->autoCanonicalize = FALSE;
$request = new Nette\Application\Request('Admin:Login', 'POST', array(
'action' => 'default',
'do' => 'loginForm-submit',
), array(
'username' => 'admin',
'password' => 'admin',
));
$response = $presenter->run($request);
Assert::true( $presenter->user->isLoggedIn() ); //vrátí False
Request je stejný jako v aplikaci, ale uživatel se přesto nepřihlásí. Asi už mi docházejí nápady.
Milo napsal(a):
@zimni: Četl jsi Davidovo velestručné testování prezenterů?
K Testeru… Tester vypisuje hezký stack trace, kde k chybné aserci došlo. Jen nesmíš zapomenout v každém testu zavolat
Tester\Environment::setup()
(resp.Tester\Helpers::setup()
ve staší verzi). Testy můžeš spouštět i samostatně jako obyčejný PHP skript. To se dobře hodí pro ladění.php tests/my-test.phpt
- zimmi
- Člen | 94
Ahoj,
tak po víkendu jsem se k tomu dostal. To vyhazování výjimek pomohlo,
zjistil jsem, že kvůli $form->addProtection() se ten formulář nikdy
nedostal ke callbacku onSuccess.
Metoda loginFormSent vypadá takhle:
function loginFormSent(\LoginForm $form)
{
//throw new \Exception('FormSent');
$user = $this->user;
$values = $form->values;
try{
$user->login($values->username, $values->password);
if ($values->remember) {
$user->setExpiration('+30 days', False);
}
Debugger:dump($this->user->isLoggedIn());
$this->redirect('User:list');
} catch (NS\AuthenticationException $e) {
$this->flashMessage($e->getMessage(), 'error');
$this->redirect('this');
}
}
Dump vrací v testu True, ale assert pořád False.
Edit: Pokud zakomentuju přesměrování v try bloku, assert vrací True. Co to znamená, respektive jak to mám chápat? To přesměrování v testu neudrží přihlášeného uživatele?
Editoval zimmi (5. 8. 2013 10:15)
- Milo
- Nette Core | 1283
Ačkoliv s tím nemám osobní zkušenost, odhaduji že problém bude v session. Když se user přihlásí, info se uloží do session a ta se udržuje pomocí cookiesky. Při redirectu se Ti ale už session neobnoví.
Myslím že na to, že se user úspěšně přihlásil, by Ti mělo stačit
otestovat správný redirect na User:list
+ funkci autorizátoru.
Takhle testuješ formulář a autorizátor najednou.
- zimmi
- Člen | 94
@Milo: Teď jsem teda v situaci, kdy mám otestované přihlašování, které je funkční. Takže teď mi v podstatě stačí k těm @Secured metodám vždy vytvořit fake uživatele, na kterém už pak jen otestuju authorizator, jo? O těch session jsem vlastně někde četl, že se dají nahradit vlastní implementací IUserStorage. Když tak na to ještě mrknu, ale jinak to asi zkusím tak, jak jsem napsal.
Každopádně děkuju za rady, už by to snad mělo šlapat.
- zimmi
- Člen | 94
Tak ještě poslední dotaz:
Adresářová struktura aplikace
-app
-libs
-Grido
-nette
-Nette
-tester
-Tester/tester.php
-tests
Když testuju šablonu, ve které je vykreslené Grido, dostanu chybu
Exited with error code 255 (expected 0) Class Grido\Grid not found on line...
.
Test spouštím příkazem
php -c /etc/php5/apache2/php.ini libs/nette/tester/Tester/tester.php -c /etc/php5/apache2/php.ini tests/UserPresenterTest.phpt
Lze tohle nějak vyřešit? Zkoušel jsem do bootstrapu require Grid.php, což začne vyžadovat require dalších částí Grida, až nakonec skončím u toho, že nejde načíst interface. Tohle asi nebude ta správná cesta.