FriendsOfBehat / SymfonyExtension

:musical_score: Extension integrating Behat with Symfony.
MIT License
472 stars 62 forks source link

using "Behatch\Context\RestContext" cause The service has a dependency on a non-existent service "behatch.http_call.request" #111

Open masoud91 opened 4 years ago

masoud91 commented 4 years ago

Hey guys, Thanks for your awesome work. I'm trying to use Behatch RestContext in my context but I get the following error:

The service "App\Tests\Behat\FeatureContext" has a dependency on a non-existent service "behatch.http_call.request".

Am I wrong somewhere?

This is my configuration: symfony: 4.4 composer.json:

"require-dev": {
        "behat/behat": "^3.6",
        "behat/mink": "^1.7@dev",
        "behat/mink-browserkit-driver": "^1.3",
        "behat/mink-extension": "^2.3",
        "behatch/contexts": "^3.3",
        "coduo/php-matcher": "^4.0",
        "doctrine/doctrine-fixtures-bundle": "^3.3",
        "friends-of-behat/symfony-extension": "^2.0.0",
        "fzaninotto/faker": "^1.9",
        "symfony/debug-pack": "*",
        "symfony/maker-bundle": "^1.0",
        "symfony/profiler-pack": "*",
        "symfony/test-pack": "*"
    }

behat.yml

default:
    suites:
        default:
            contexts:
                - Behat\MinkExtension\Context\MinkContext
                - behatch:context:browser
                - behatch:context:debug
                - behatch:context:system
                - behatch:context:json
                - behatch:context:table
                - behatch:context:rest
                - behatch:context:xml

    extensions:
        FriendsOfBehat\SymfonyExtension: ~
        Behatch\Extension: ~
        Behat\MinkExtension:
            base_url:  'http://localhost:8000'
            sessions:
                symfony:
                    symfony: ~

service_test.yml

services:
    _defaults:
        autowire: true
        autoconfigure: true

    App\Tests\Behat\FeatureContext:
        public: true
        arguments:
            - '@behatch.http_call.request'
            - '@App\DataFixtures\AppFixtures'
            - '@doctrine.orm.default_entity_manager'

#    App\Tests\Behat\:
#        resource: '../tests/Behat/*'

app/tests/Behat/FeatureContext.php

<?php

namespace App\Tests\Behat;

use Behatch\Context\RestContext;
use Coduo\PHPMatcher\Factory\SimpleFactory;
use Doctrine\Common\DataFixtures\Executor\ORMExecutor;
use Doctrine\Common\DataFixtures\Purger\ORMPurger;
use Doctrine\ORM\Tools\SchemaTool;

/**
 * This context class contains the definitions of the steps used by the demo
 * feature file. Learn how to get started with Behat and BDD on Behat's website.
 *
 * @see http://behat.org/en/latest/quick_start.html
 */
final class FeatureContext extends RestContext
{

    /**
     * @var \App\DataFixtures\AppFixtures
     */
    private $fixtures;

    /**
     * @var \Coduo\PHPMatcher\Matcher
     */
    private $matcher;

    /**
     * @var \Doctrine\ORM\EntityManagerInterface
     */
    private $em;

    public function __construct(
        \Behatch\HttpCall\Request $request,
        \App\DataFixtures\AppFixtures $fixtures,
        \Doctrine\ORM\EntityManagerInterface $em
    ) {
        parent::__construct($request);

        $this->fixtures = $fixtures;
        $this->matcher = (new SimpleFactory())->createMatcher();
        $this->em = $em;
    }

    /**
     * @BeforeScenario @createSchema
     */
    public function createSchema(){
        // .....
    }
}
GrzegorzMatuszakTSH commented 4 years ago

I have the same problem. As a hotfix I added in my services_test.yaml manual configuration:

    behatch.http_call.request:
        class: Behatch\HttpCall\Request
        arguments:
            - '@behat.mink'
        public: false

Original path with configuration: behatch/contexts/src/Resources/services/http_call.yml

CharloMez commented 4 years ago

I have the same issue. The problem is that behatch and symfony-extension have two way to inject dependencies.

You can inject Behatch dependencies through behat.yml like this:

                - App\Behat\Context\JsonExtendedContext:
                    httpCallResultPool: 'behatch:behatch.http_call.result_pool'
                    request: 'behatch:behatch.http_call.request'

Because Behatch provide a resolver to behat HttpCallResultPoolResolver

But symfony-extension only provide dependency injection for context as service... so: in behat.yaml this doesn't work (because symfony don't provide resolver to behat)

                - App\Behat\Context\JsonExtendedContext:
                    httpCallResultPool: 'behatch:behatch.http_call.result_pool'
                    request: 'behatch:behatch.http_call.request'
                    kernel: "@kernel"

in service_test.yaml this doesn't work (because behatch service are private and can only be injected through behat Resolver

    App\Behat\Context\JsonExtendedContext:
        $httpCallResultPool: "@behatch.http_call.result_pool"
        $kernel: "@kernel"

@GrzegorzMatuszakTSH I'm not sure your solution is the best, because you provide a new reference of this object, I'm not sure that internally behatch will use the same instance. So between your context and behatch context, informations will be differents.

This is a real issue.

I think there is two way to handle this:

GrzegorzMatuszakTSH commented 4 years ago

@CharloMez Yes, I agree with you. As I wrote my "solution" was a hotfix and we need a change in the library to fix it in the right way. Your ideas are very good :)

pamil commented 4 years ago

@CharloMez would you be willing to contribute the second solution?

CharloMez commented 4 years ago

@pamil I'm trying, but I don't find how to get all ArgumentResolver... when I try in compiler pass

use Behat\Behat\Context\ServiceContainer\ContextExtension;
.....

$container->findTaggedServiceIds(ContextExtension::ARGUMENT_RESOLVER_TAG);

I got nothing, but behat find them in it's extension here https://github.com/Behat/Behat/blob/98cfd077dcb68a6f1e20f3faa94b31e63b0dd182/src/Behat/Behat/Context/ServiceContainer/ContextExtension.php#L279

I think I miss something in symfony process. It is probably not the same container which is injected in behat extension, but I don't know how to access to the same container from SymfonyExtension. any idea how to get these services ?

ymarillet commented 4 years ago

I tried many other solutions (extending JsonContext, calling the behatch internal container to get the JsonContext, ...) but only one worked (that's dirty though): Reflection


<?php

namespace App\Tests\Behat;

use Behat\Behat\Context\Context;
use Behat\Behat\Hook\Scope\BeforeScenarioScope;
use Behatch\Context\JsonContext;
use Behatch\HttpCall\HttpCallResultPool;
use Behatch\Json\JsonInspector;

class JsonExtendedContext implements Context
{
    private HttpCallResultPool $httpCallResultPool;

    private JsonInspector $inspector;

    /**
     * @BeforeScenario
     */
    public function beforeScenario(BeforeScenarioScope $scope)
    {
        $jsonContext = $scope->getEnvironment()->getContext(JsonContext::class);
        $refl = new \ReflectionClass(JsonContext::class);

        $httpCallResultPoolProperty = $refl->getProperty('httpCallResultPool');
        $inspectorProperty = $refl->getProperty('inspector');

        $httpCallResultPoolProperty->setAccessible(true);
        $inspectorProperty->setAccessible(true);

        $this->httpCallResultPool = $httpCallResultPoolProperty->getValue($jsonContext);
        $this->inspector = $inspectorProperty->getValue($jsonContext);
    }

    // your steps here ...
aleksbrgt commented 4 years ago

Given the example from the original @masoud91 comment, it seems you are only using the Request to forward it to the parent __constructor , you can simply avoid to extend the RestContext and only implement Behat\Behat\Context\Context.

However this is not resolving the issue of getting the Request for ourselves.

gonzalovilaseca commented 3 years ago

@pamil I'm happy to contribute the second solution if this is still needed, I implemented an extension on top of the original sf2 extension and it doesn't work with this one, so if you give me some guidance I can contribute.

nathansalter commented 3 years ago

I managed to wrangle a working version by doing this:

services_test.yaml:

services:
  defaults:
    autowire: true
    autoconfigure: true
    bind:
      $behatContainer: '@test.service_container'

  App\Tests\Context\:
    resource: '../tests/Context/*'
    public: true

JsonContext.php:

class JsonContext extends BaseContext
{
    private ContainerInterface $behatContainer;

    public function __construct(ContainerInterface $behatContainer, string $evaluationMode = 'javascript')
    {
        $this->behatContainer = $behatContainer;
    }

    /** @BeforeScenario */
    public function gatherContexts(BeforeScenarioScope $scope): void
    {
        $container = $this->behatContainer->get('behat.service_container');
        $this->httpCallResultPool = $container->get('behatch.http_call.result_pool');
    }
}

However, using this method I couldn't get a working version to override RestContext. I think this is because the request isn't actually put into the container, so there's no method to get the current Behat request. Definitely requires more work.

For other services, here's the list of available services that I found in the container:

{
    "cli.input": "Symfony\\Component\\Console\\Input\\ArgvInput",
    "cli.output": "Symfony\\Component\\Console\\Output\\ConsoleOutput",
    "translator": "Behat\\Behat\\Definition\\Translator\\Translator",
    "fob_symfony.kernel": "App\\Kernel",
    "fob_symfony.mink": "FriendsOfBehat\\SymfonyExtension\\Mink\\Mink",
    "fob_symfony.driver_kernel": "App\\Kernel",
    "behatch.http_call.result_pool": "Behatch\\HttpCall\\HttpCallResultPool",
    "definition.pattern_transformer": "Behat\\Behat\\Definition\\Pattern\\PatternTransformer",
    "environment.manager": "Behat\\Testwork\\Environment\\EnvironmentManager",
    "argument.mixed_organiser": "Behat\\Testwork\\Argument\\MixedArgumentOrganiser",
    "call.center": "Behat\\Testwork\\Call\\CallCenter",
    "cli.command": "Behat\\Testwork\\Cli\\Command"
}