Nette Tester + Doctrine 2 – jak v testu vytvořit QueryBuilder?

svatekr
Člen | 7
+
0
-

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

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

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

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.