php - Behat scenario failing if run with other scenarios -
i have rather odd 1 here today...
i have behat feature file contains several scenarios. each of scenarios pass if run individually, if run feature file in it's entirety, 1 of tests fails, error...
notice: undefined index: 00000000070885f90000000106598262 in /project/vendor/doctrine/orm/lib/doctrine/orm/unitofwork.php line 2058
... (shown below)
the scenarios load fixtures, , navigates around pages using information in fixture entity, , check page urls correct.
the weird thing second test fails, if first 1 run before it. if it's not, context manages store fixture entity property, , use entitymanager::merge() , entitymanager::refresh() reload entity it's current state. when first test run before it, context still fetching , storing fixture entity in same way, when attempts merge , refresh, reason entity manager unit of work seems have forgotten it.
before each scenario d/b cleared, , fixtures reloaded, using code shown below. i've made sure i've called entitymanager::clear() ensure remnants of previous test removed.
/** * clears d/b * * @throws toolsexception */ public function cleardb() { foreach ($this->getentitymanagers() $entitymanager) { $metadata = $this->getmetadata($entitymanager); if (!empty($metadata)) { $tool = new schematool($entitymanager); $tool->dropschema($metadata); $tool->createschema($metadata); } } }
a bit more info investigation...
having investigated further, it's not problem if first test fetches entity, stores it, not request page (using mink)
files...
behat test (with annotations)
@fix:application\stage9submitted\submittedstage1 @fix:user\fundadmin\fundadmin1 scenario: can assign application case worker given logged in "user\fundadmin\fundadmin1" fixture user , on application admin "eligibility" page "application\stage9submitted\submittedstage1" fixture application ^== fetches amd saves $currententity , should see "unassigned" in ".application-summary .case-worker" element when follow "change case worker" , select "fund.admin@example.com" "project_application_admin_change_caseworker_caseworker" , press "change case worker" should on application admin "eligibility" page application ^== retrieves $currententity , calls entitymanager::merge() , entitymanager::refresh() ^== works , should see "fund admin" in ".application-summary .case-worker span[title='fund.admin@example.com']" element , should see "application assigned fund admin" @fix:application\stage9submitted\submittedstage1 @fix:user\fundadmin\fundadmin1 scenario: can un-assign application case worker given logged in "user\fundadmin\fundadmin1" fixture user , on application admin "eligibility" page "application\stage9submitted\submittedstage1" fixture application ^== fetches amd saves $currententity , should see "unassigned" in ".application-summary .case-worker" element , follow "change case worker" , select "fund.admin@example.com" "project_application_admin_change_caseworker_caseworker" , press "change case worker" , should on application admin "eligibility" page application ^== retrieves $currententity , calls entitymanager::merge() , entitymanager::refresh() ^== fails (but if above test run @ same time!?!) ...
fixturescontext
<?php namespace cubicmushroom\symfonyfeaturecontextbundle\feature\context; use behat\behat\hook\scope\beforescenarioscope; use behat\symfony2extension\context\kernelawarecontext; use doctrine\common\datafixtures\abstractfixture; use doctrine\common\datafixtures\executor\ormexecutor; use doctrine\common\datafixtures\fixtureinterface; use doctrine\common\datafixtures\loader; use doctrine\common\datafixtures\purger\ormpurger; use doctrine\common\datafixtures\referencerepository; use doctrine\dbal\connection; use doctrine\orm\entitymanager; use doctrine\orm\tools\schematool; use doctrine\orm\tools\toolsexception; use project\datafixtures\orm\abstractsinglefixture; use project\exception\feature\context\fixturecontext\fixturenotfoundexception; use symfony\component\dependencyinjection\containerinterface; use symfony\component\httpkernel\kernelinterface; /** * loads fixtures based on scenario tags * * @package project */ class fixturescontext implements kernelawarecontext { // ----------------------------------------------------------------------------------------------------------------- // properties // ----------------------------------------------------------------------------------------------------------------- /** * @var kernelinterface */ protected $kernel; /** * @var array */ protected $fixturenamespaces; /** * @var loader */ protected $loader; /** * @var abstractfixture[] */ protected $loadedfixtures; /** * @var ormexecutor */ protected $executor; /** * newfixturescontext constructor. * * @param array $fixturenamespaces */ public function __construct(array $fixturenamespaces) { foreach ($fixturenamespaces $fixturenamespace) { $this->addfixturenamespace($fixturenamespace); } } // ----------------------------------------------------------------------------------------------------------------- // @beforescenario // ----------------------------------------------------------------------------------------------------------------- /** * @beforescenario * * @param beforescenarioscope $scope */ public function loadfixturesfromtags(beforescenarioscope $scope) { // load here, rather in constructor it's re-initialised on each scenario $this->loader = new loader(); $tags = $scope->getscenario()->gettags(); foreach ($tags $tag) { $this->loadfixturesfortag($this->loader, $tag); } $fixtures = $this->loader->getfixtures(); if (empty($fixtures)) { return; } $this->cleardb(); $em = $this->getentitymanager(); $em->clear(); $purger = new ormpurger(); $this->executor = new ormexecutor($em, $purger); $this->executor->purge(); $this->executor->execute($fixtures, true); $this->loadedfixtures = $fixtures; } /** * @param string $fixture * * @return array */ public function getnamespacedfixtures($fixture) { $fixtures = []; foreach ($this->fixturenamespaces $fixturenamespace) { $fixtureclass = "{$fixturenamespace}\\{$fixture}"; if (class_exists($fixtureclass)) { $fixtures[] = $fixtureclass; } } return $fixtures; } /** * clears d/b * * @throws toolsexception */ public function cleardb() { foreach ($this->getentitymanagers() $entitymanager) { $metadata = $this->getmetadata($entitymanager); if (!empty($metadata)) { $tool = new schematool($entitymanager); $tool->dropschema($metadata); $tool->createschema($metadata); } } } /** * loads fixtures given tag * * @param loader $loader * @param string $tag */ protected function loadfixturesfortag(loader $loader, $tag) { $parts = explode(':', $tag); $prefix = array_shift($parts); // bother tags staring 'fix:' if ('fix' !== $prefix) { return; } if (empty($parts)) { throw new \logicexception('no fixture provided'); } $fixture = array_shift($parts); $args = $parts; $fixtureclasses = $this->getnamespacedfixtures($fixture); foreach ($fixtureclasses $fixtureclass) { $reflect = new \reflectionclass($fixtureclass); $instance = $reflect->newinstanceargs($args); if (!$instance instanceof fixtureinterface) { throw new \invalidargumentexception("class {$fixtureclass} not implement fixtureinterface"); } $loader->addfixture($instance); return; } throw fixturenotfoundexception::create($fixture); } /** * @afterscenario * * * @return null */ public function closedbalconnections() { /** @var entitymanager $entitymanager */ foreach ($this->getentitymanagers() $entitymanager) { $entitymanager->clear(); } /** @var connection $connection */ foreach ($this->getconnections() $connection) { $connection->close(); } } // ----------------------------------------------------------------------------------------------------------------- // getters , setters // ----------------------------------------------------------------------------------------------------------------- /** * @param $fixturesdir * * @return $this */ protected function addfixturenamespace($fixturesdir) { if (!isset($this->fixturenamespaces)) { $this->fixturenamespaces = []; } if (!in_array($fixturesdir, $this->fixturenamespaces)) { $this->fixturenamespaces[] = $fixturesdir; } return $this; } /** * sets kernel instance. * * @param kernelinterface $kernel */ public function setkernel(kernelinterface $kernel) { $this->kernel = $kernel; } /** * @return containerinterface */ protected function getcontainer() { return $this->kernel->getcontainer(); } /** * @param entitymanager $entitymanager * * @return array */ protected function getmetadata(entitymanager $entitymanager) { return $entitymanager->getmetadatafactory()->getallmetadata(); } /** * @return array */ protected function getentitymanagers() { return $this->getcontainer()->get('doctrine')->getmanagers(); } /** * @return entitymanager */ protected function getentitymanager() { $em = $this->kernel->getcontainer()->get('doctrine.orm.entity_manager'); return $em; } /** * @return connection[] */ protected function getconnections() { return $this->kernel->getcontainer()->get('doctrine')->getconnections(); } /** * @return ormexecutor */ public function getexecutor() { return $this->executor; } /** * @return referencerepository */ public function getreferencerepository() { return $this->executor->getreferencerepository(); } /** * @param string $fixtureclass * * @return fixtureinterface * * @throws \outofboundsexception if fixture not found */ public function getfixture($fixtureclass) { try { $userfixture = $this->_getfixture($fixtureclass); } catch (\outofboundsexception $exception) { $fixtures = $this->getnamespacedfixtures($fixtureclass); if (empty($fixtures)) { throw new \outofboundsexception("fixture {$fixtureclass} not found"); } if (count($fixtures) > 1) { throw new \logicexception( "found multiple {$fixtureclass} fixtures. use full namespace correct" ); } /** @var abstractsinglefixture $userfixture */ $userfixture = $this->_getfixture($fixtures[0]); } return $userfixture; } /** * @param string $fixtureclass * * @return fixtureinterface * * @throws \outofboundsexception if fixture not found */ protected function _getfixture($fixtureclass) { foreach ($this->loader->getfixtures() $fixture) { if (is_a($fixture, $fixtureclass)) { return $fixture; } } throw new \outofboundsexception("fixture '{$fixtureclass}' not found'"); } /** * @param $fixtureclass * * @return object * * @throw \outofboundsexception if fixture not found */ public function getfixtureentity($fixtureclass) { // fixture class shorthand, without namespace, use getfixture full class name… $fixture = $this->getfixture($fixtureclass); $fixtureclass = get_class($fixture); $referencerepository = $this->getreferencerepository(); if (!$referencerepository->hasreference($fixtureclass)) { throw new \outofboundsexception("fixture '{$fixtureclass}' not found"); } return $referencerepository->getreference($fixtureclass); } }
unitofwork.php
# /project/vendor/doctrine/orm/lib/doctrine/orm/unitofwork.php showing line 2058 # (marked on rh side of code) <?php namespace doctrine\orm; use ... class unitofwork implements propertychangedlistener { // ... /** * executes refresh operation on entity. * * @param object $entity entity refresh. * @param array $visited visited entities during cascades. * * @return void * * @throws orminvalidargumentexception if entity not managed. */ private function dorefresh($entity, array &$visited) { $oid = spl_object_hash($entity); if (isset($visited[$oid])) { return; // prevent infinite recursion } $visited[$oid] = $entity; // mark visited $class = $this->em->getclassmetadata(get_class($entity)); if ($this->getentitystate($entity) !== self::state_managed) { throw orminvalidargumentexception::entitynotmanaged($entity); } $this->getentitypersister($class->name)->refresh( array_combine($class->getidentifierfieldnames(), $this->entityidentifiers[$oid]), <===== line 2058 $entity ); $this->cascaderefresh($entity, $visited); } // ... }
i'll not post context classes here, if need more info, please let me know.
any or pointers appreciated.
many thanks.
probably scenarios aren't "teared down" correctly.
i have solutions problem:
1) slow approach
recreate db data (so, load fixtures basically) each time run new scenario
2) faster approach
run every scenario in transaction , discard changes after each scenario has finished
your tests should isolated , never affected other test runs before or after them.
Comments
Post a Comment