如果与其他方案一起运行,则Behat场景失败

时间:2016-02-05 16:19:12

标签: php symfony doctrine behat

我今天在这里有一个相当奇怪的...

我有一个包含多个场景的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);
    }

    // ...
}

我不会在此发布所有上下文类,但如果您需要更多信息,请告知我们。

任何有关此的帮助或指示都将不胜感激。

非常感谢。

1 个答案:

答案 0 :(得分:0)

可能你的情景没有被删除&#34;正确。

我必须解决这个问题:

1)慢速方法

每次运行新方案时重新创建数据库数据(因此,基本上加载灯具)

2)更快的方法

运行事务中的每个方案,并在每个方案完成后丢弃所有更改

您的测试应该被隔离,并且不会受到在其之前或之后运行的其他测试的影响。