Nette Tester + Doctrine 2 – jak v testu vytvořit QueryBuilder?
- svatekr
- Člen | 7
Jsem vysloveně amatér, a s PHP se jen bavím. Snažím se naučit testovat. Nette Tester se mi líbí, stejně jako všechno, co se okolo Nette točí. Teď k mému problému – mám třídu pro výběr uživatelů:
final class UserSelectFacade extends BaseSelectFacade
{
/**
* @param EntityManager $em
*/
public function __construct(EntityManager $em)
{
parent::__construct($em);
$this->em = $em;
}
public function getPairs()
{
$qb = $this->em->createQueryBuilder();
$pairs = $qb->select("u.id, CONCAT(u.lastName, ' ', u.firstName) AS name")
->from(User::class, 'u')
->orderBy('u.lastName')
->getQuery()
->setResultCacheProfile(new QueryCacheProfile())
->useQueryCache(true)
->getArrayResult();
return Arrays::associate($pairs, 'id=name');
}
}
Funguje správně a vrací pole s id a celým jménem uživatelů. Snažím se udělat test, který mi potvrdí, že se vrací opravdu pole:
require __DIR__ . '/../bootstrap.php';
class User extends TestCase
{
public function getPairsTest()
{
$em = Mockery::mock('Nettrine\ORM\EntityManagerDecorator');
$userFacade = new UserSelectFacade($em);
Assert::type('array', $userFacade->getPairs());
}
}
(new User)->getPairsTest();
V bootstrap.php si tahám config se službami, takže EntityManager a
UserSelectFacade se mi vyrobí. Ale celé to končí při pokusu vytvořit
QueryBuilder chybovou hláškou:
Mockery\Exception\BadMethodCallException: Received
Mockery0_Nettrine_ORM_EntityManagerDecorator::createQueryBuilder(),
but no expectations were specified
Hledal jsem tady v diskuzích i jinde, ale řešení stále uniká.
Tuším, že se to bude týkat Mockování, ale nevím, jak toho docílit.
Pomůže někdo ze znalců?
Mimochodem – když volám
(new User)->run();
namísto
(new User)->getPairsTest();
tak test se vůbec nezavolá, resp. končí hláškou „Error: This test forgets to execute an assertion.“
Díky za nakopnutí.
- Marek Bartoš
- Nette Blogger | 1263
Mock je vlastně jen zkopírované rozhraní objektu bez původní vnitřní implementace. Sám o sobě nedělá vůbec nic a proto ti nefunguje. Buď si můžeš zvolit, že se zkopíruje i vnitřní implementace (nebo spíš její část, jinak by mock nedával smysl) nebo napíšeš implementaci pro test. Pro implementaci pro test volíš, kolikrát by se měla metoda volat, s jakými vstupními parametry a co vrátit (například další mock).
Test doubles jsou užitečné, ať už pro rychlost nebo otestování
komplikovaného chování, ale mockování je imho „I am too lazy to test
this“. Když místo objektu vytvoříš jeho mock, tak jsi objekt vůbec
neotestoval. V tém případě mockuješ entity manager – ten vrací query
builder a pomocí builderu sestavuješ dotaz vracející data. Můžeš sice
mocku nastavit, že voláš createQueryBuilder()
, což ti vrátí
mock query builderu a na tom zase nastavit, jaké na něm voláš metody a co ti
vrátí za data – ale co tím otestuješ? Jaké přesně voláš na builderu
metody a v jaké posloupnosti? Neotestuješ tak, zda se ti vygeneroval validní
dotaz a zda by z databáze něco vrátil. Máš sice kód „pokrytý testy“,
ale reálně jsi neotestoval vůbec nic.
Lepší řešení je být jak při vývoji, tak v testech, co nejblíže produkční verzi aplikace. Spustit v testech databázi a vyzkoušet, že ten dotaz opravdu funguje. Žádné komplikované mocky a kód v testech takřka stejný co v aplikaci.
Error: This test forgets to execute an assertion.
Myslím že test metody musí začínat na test
, tzn.
test()
nebo test*()
. Je třeba dodržet naming
pattern.
'Nettrine\ORM\EntityManagerDecorator'
Na třídy se můžeš odkazovat takto
Nettrine\ORM\EntityManagerDecorator::class
, není třeba je psát
do stringu
Editoval Marek Bartoš (18. 7. 2021 21:01)
- svatekr
- Člen | 7
Tak jsem to trochu přepsal. Z bootstrapu si vracím $container. Ten pak předám přes DI a tu Facade si vytáhnu z něj:
$container = require __DIR__ . '/../bootstrap.php';
class User extends TestCase
{
/**
* @param Container $container
*/
public function __construct(Container $container)
{
$this->container = $container;
}
public function testGetPairs()
{
$userFacade = $this->container->getByType('\UserModule\Model\Facade\UserSelectFacade');
Assert::type('array', $userFacade->getPairs());
}
}
(new User($container))->run();
Test se provede (díky za upozornění na tu konvenci pro pojmenování testů) a zahlásí OK. Díky za posun. Jdu to zkoušet na nějakých smysluplnějších příkladech.
- Petr Parolek
- Člen | 455
Ahoj, na testování se mi osvědčil Mango tester. Příklad je tu https://github.com/…mango-tester. Jde o to, že si vytvoříš testovací databáze s daty pomocí migrací a testuješ přímo, co ti jde z a do databáze.