我今天在这里有一个相当奇怪的...
我有一个包含多个场景的Behat功能文件。如果我单独运行每个场景都将通过,但是如果我完整地运行该功能文件,则其中一个测试失败,错误...
Notice: Undefined index: 00000000070885f90000000106598262 in /project/vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php line 2058
......(如下所示)
场景加载灯具,然后使用灯具实体中的信息在页面周围导航,然后检查页面网址是否正确。
奇怪的是,第二次测试失败,只有第一次测试失败才会运行。如果不是,则上下文设法将fixture实体成功存储为属性,然后使用EntityManager :: merge()和EntityManager :: refresh()将实体重新加载到其当前状态。当第一个测试在它之前运行时,上下文仍然以相同的方式获取和存储fixture实体,但是当它尝试合并和刷新时,由于某种原因,实体管理器工作单元似乎忘记了它。 / p>
在每个场景之前,清除d / b,并使用下面显示的代码重新加载灯具。我还确保我已调用EntityManager :: clear()以确保删除先前测试的任何残余。
/**
* Clears the d/b
*
* @throws ToolsException
*/
public function clearDb()
{
foreach ($this->getEntityManagers() as $entityManager) {
$metadata = $this->getMetadata($entityManager);
if (!empty($metadata)) {
$tool = new SchemaTool($entityManager);
$tool->dropSchema($metadata);
$tool->createSchema($metadata);
}
}
}
我调查的更多信息......
进一步调查后,如果第一个测试只是获取实体,存储它,但不请求页面(使用Mink),则不是问题
文件...
Behat测试(带注释)
@fix:Application\Stage9Submitted\SubmittedStage1 @fix:User\FundAdmin\FundAdmin1
Scenario: I can assign an application to a case worker
Given I am logged in as "User\FundAdmin\FundAdmin1" fixture user
And I am on the application admin "eligibility" page for "Application\Stage9Submitted\SubmittedStage1" fixture application
^== fetches amd saves as $currentEntity
And I should see "Unassigned" in the ".application-summary .case-worker" element
When I follow "Change case worker"
And I select "fund.admin@example.com" from "project_application_admin_change_caseworker_caseWorker"
And I press "Change case worker"
Then I should be on the application admin "eligibility" page for that application
^== Retrieves $currentEntity and calls EntityManager::merge() and EntityManager::refresh()
^== This works
And I should see "Fund Admin" in the ".application-summary .case-worker span[title='fund.admin@example.com']" element
And I should see "Application assigned to Fund Admin"
@fix:Application\Stage9Submitted\SubmittedStage1 @fix:User\FundAdmin\FundAdmin1
Scenario: I can un-assign an application from a case worker
Given I am logged in as "User\FundAdmin\FundAdmin1" fixture user
And I am on the application admin "eligibility" page for "Application\Stage9Submitted\SubmittedStage1" fixture application
^== fetches amd saves as $currentEntity
And I should see "Unassigned" in the ".application-summary .case-worker" element
And I follow "Change case worker"
And I select "fund.admin@example.com" from "project_application_admin_change_caseworker_caseWorker"
And I press "Change case worker"
And I should be on the application admin "eligibility" page for that application
^== Retrieves $currentEntity and calls EntityManager::merge() and EntityManager::refresh()
^== This fails (but only if the above test run at the 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 as $fixtureNamespace) {
$this->addFixtureNamespace($fixtureNamespace);
}
}
// -----------------------------------------------------------------------------------------------------------------
// @BeforeScenario
// -----------------------------------------------------------------------------------------------------------------
/**
* @BeforeScenario
*
* @param BeforeScenarioScope $scope
*/
public function loadFixturesFromTags(BeforeScenarioScope $scope)
{
// We load this here, rather than in the constructor so it's re-initialised on each scenario
$this->loader = new Loader();
$tags = $scope->getScenario()->getTags();
foreach ($tags as $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 as $fixtureNamespace) {
$fixtureClass = "{$fixtureNamespace}\\{$fixture}";
if (class_exists($fixtureClass)) {
$fixtures[] = $fixtureClass;
}
}
return $fixtures;
}
/**
* Clears the d/b
*
* @throws ToolsException
*/
public function clearDb()
{
foreach ($this->getEntityManagers() as $entityManager) {
$metadata = $this->getMetadata($entityManager);
if (!empty($metadata)) {
$tool = new SchemaTool($entityManager);
$tool->dropSchema($metadata);
$tool->createSchema($metadata);
}
}
}
/**
* Loads the fixtures for a given tag
*
* @param Loader $loader
* @param string $tag
*/
protected function loadFixturesForTag(Loader $loader, $tag)
{
$parts = explode(':', $tag);
$prefix = array_shift($parts);
// Only bother with 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 as $fixtureClass) {
$reflect = new \ReflectionClass($fixtureClass);
$instance = $reflect->newInstanceArgs($args);
if (!$instance instanceof FixtureInterface) {
throw new \InvalidArgumentException("Class {$fixtureClass} does not implement FixtureInterface");
}
$loader->addFixture($instance);
return;
}
throw FixtureNotFoundException::create($fixture);
}
/**
* @AfterScenario
*
*
* @return null
*/
public function closeDBALConnections()
{
/** @var EntityManager $entityManager */
foreach ($this->getEntityManagers() as $entityManager) {
$entityManager->clear();
}
/** @var Connection $connection */
foreach ($this->getConnections() as $connection) {
$connection->close();
}
}
// -----------------------------------------------------------------------------------------------------------------
// Getters and 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 the full namespace to 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() as $fixture) {
if (is_a($fixture, $fixtureClass)) {
return $fixture;
}
}
throw new \OutOfBoundsException("Fixture '{$fixtureClass}' not found'");
}
/**
* @param $fixtureClass
*
* @return object
*
* @throw \OutOfBoundsException if fixture is not found
*/
public function getFixtureEntity($fixtureClass)
{
// Fixture class could be a shorthand, without namespace, so we use getFixture to get the 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 a refresh operation on an entity.
*
* @param object $entity The entity to refresh.
* @param array $visited The already visited entities during cascades.
*
* @return void
*
* @throws ORMInvalidArgumentException If the entity is 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);
}
// ...
}
我不会在此发布所有上下文类,但如果您需要更多信息,请告知我们。
任何有关此的帮助或指示都将不胜感激。
非常感谢。
答案 0 :(得分:0)
可能你的情景没有被删除&#34;正确。
我必须解决这个问题:
1)慢速方法
每次运行新方案时重新创建数据库数据(因此,基本上加载灯具)
2)更快的方法
运行事务中的每个方案,并在每个方案完成后丢弃所有更改
您的测试应该被隔离,并且不会受到在其之前或之后运行的其他测试的影响。