Search code examples
phpsymfonytestingbehatmink

Symfony2 Behat Extending Contexts will result in "Step is already defined"


I am trying to write some Behat tests for an application and I need to separate the Contexts so I can use some core elements in different other contexts for example the logged in user step.

behat.yml

suites:
    common:
        type: symfony_bundle
        bundle: CommonBundle
        mink_session: symfony2
        mink_javascript_session: selenium2
        autoload:
            'CommonContext': %paths.base%/src/xxx/CommonBundle/Features/Context
        contexts: [ xxx\CommonBundle\Features\Context\CoreContext ]

    user:
        type: symfony_bundle
        bundle: UserBundle
        mink_session: symfony2
        mink_javascript_session: selenium2
        autoload:
            'UserContext': %paths.base%/src/xxx/UserBundle/Features/Context
        contexts:
            - Behat\MinkExtension\Context\MinkContext
            - xxx\CommonBundle\Features\Context\CoreContext
            - xxx\UserBundle\Features\Context\UserContext

DefaultContext.php

namespace XXX\CommonBundle\Features\Context;

use ...

/**
 * Class DefaultContext
 */
class DefaultContext extends MinkContext implements Context, KernelAwareContext
{

    /**
     * @param AbstractEntity $oEntity
     *
     * @return object
     */
    protected function getRepository(AbstractEntity $oEntity)
    {
        return $this->getService($oEntity);
    }

    /**
     * @return mixed
     */
    protected function getEntityManager()
    {
        return $this->getService('doctrine')->getManager();
    }

    /**
     * @param $id
     *
     * @return object
     */
    protected function getService($id)
    {
        return $this->getContainer()->get($id);
    }

CoreContext.php

namespace XXX\CommonBundle\Features\Context;

use ...

/**
 * Class SubContext
 */
class CoreContext extends DefaultContext implements Context, SnippetAcceptingContext
{

    /**
     * @Given I visit the homepage
     * @When I visit the homepage
     */
    public function iVisitHomepage()
    {
        $this->visitPath('/');
    }

    /**
     * @And /^I am logged in as "([^"]*)"$/
     * @Then /^I am logged in as "([^"]*)"$/
     */
    public function iAmLoggedInAs($username)
    {
        $user = $this->getContainer()->get('fos_user.user_manager')->findUserByUsername($username);

        $this->visit('/login');

        $this->fillField('_username', $user->getEmailCanonical());
        $this->fillField('_password', self::USER_PASSWORD);

        $this->pressButton('_submit');
    }

UserContext.php

namespace xxx\UserBundle\Features\Context;

use ...

/**
 * Defines application features from the specific context.
 */
class UserContext extends CoreContext implements Context, SnippetAcceptingContext
{

    /**
     * Initializes context.
     *
     * Every scenario gets its own context instance.
     * You can also pass arbitrary arguments to the
     * context constructor through behat.yml.
     */
    public function __construct()
    {
    }

}

register_user.feature

Feature: Register user

  @javascript
  Scenario: Register user
    Given I am on homepage
    And I go to "/register/"
    And I am logged in as "foo@bar.com"
    Then I should see "Terms and Conditions"

So when I run the test I get an error saying this:

Given I am on homepage
      Step "I visit the homepage" is already defined in xxx\CommonBundle\Features\Context\CoreContext::iVisitHomepage()

      xxx\CommonBundle\Features\Context\CoreContext::iVisitHomepage()
      xxx\CommonBundle\Features\Context\CoreContext::iVisitHomepage()
    And I go to "/register/"
      Step "I visit the homepage" is already defined in xxx\CommonBundle\Features\Context\CoreContext::iVisitHomepage()

      xxx\CommonBundle\Features\Context\CoreContext::iVisitHomepage()
      xxx\CommonBundle\Features\Context\CoreContext::iVisitHomepage()

Do i understood this completely wrong or I am missing some settings here?


Solution

  • Do not extend contexts that provide step definitions. There's no way around this.

    Good extensions provide contexts with handy methods but no step definitions. In case of the Mink extension, next to the MinkContext you also have RawMinkContext. First provides step definitions and should not be extended. The other one provides helper methods you might be interested in. The RawMinkContext is the one you should extend. Alternatively you can also use the MinkAwareTrait.