N
N
Nikita Roschenko2018-03-26 15:50:24
symfony
Nikita Roschenko, 2018-03-26 15:50:24

How to lock a service in DI in Symfony3.4 + Codeception when writing functional tests?

Good afternoon, there is a Symfony3.4 application that provides a REST API wrapper over a third-party service.
Working with a third-party service takes place in a class that is initialized in DI as a service:
AppBundle/Resources/config/services.yml

app.service.third_party_service:
        class: AppBundle\Service\ThirdPartyService
        arguments:
            - "%third_party_service.url%"
            - "@event_dispatcher"

The code of the AppBundle\Service\ThirdPartyService.php class itself
/**
 * Class ThirdPartyService
 * @package AppBundle\Service
 */
class ThirdPartyService implements ThirdPartyInterface
{
    /**
     * @param string $username
     * @param string $password
     * @return bool
     */
    public function login(string $username, string $password): bool
    {
        //some php code
    }

    /**
     * @param string $email
     * @param string $username
     * @param string $password
     * @return bool
     */
    public function register(string $email, string $username, string $password): bool
    {
        //some php code
    }
}

It is necessary to write functional Codeception tests for the REST API of the application, just so that the app.service.third_party_service service does not make requests to a third-party application, i.e. you need to lock the methods of this service to certain responses.
There is a functional tests config tests/functional.suite.yml :
actor: FunctionalTester
modules:
    enabled:
        - Symfony:
            app_path: 'app'
            environment: 'test'
            debug: true
            part: SERVICES
        - Doctrine2:
            depends: Symfony
        - REST:
            depends: PhpBrowser
            url: http://localhost/app_dev.php/api/v1
        - \Helper\Functional

There is a test itself tests/functional/AccountLoginCest.php :
class AccountLoginCest
{
    /**
     * @param FunctionalTester $I
     */
    public function testSuccessfulLogin(FunctionalTester $I)
    {
        $thirdPartyService = $I->grabService('app.service.third_party_service');

        dump($thirdPartyService);

        $I->sendPOST('/account/login', [
            'login'    => 'test',
            'password' => 'test',
        ]);

        $I->seeResponseCodeIs(HttpCode::OK);
    }
}

In the test, I accessed the service app.service.third_party_service , but I can't figure out how to make that service's login method return true for the test data.
I see two options here:
1) Hardcode in the service to check for test data, and if it is, then do not make a request to a third-party web service, but immediately return true;
2) Create an environment test , and in the app/config/config_test.yml config make the decorator app.service.third_party_service . Pass the environment itself in the tests as a parameter in the url.
Both of these options, although suitable, are very miserable from my point of view, pliz help advice how can app.service.third_party_service be blocked in a human way ?

Answer the question

In order to leave comments, you need to log in

1 answer(s)
D
Denis, 2018-03-27
@Avillions

It is not the service that needs to be replaced in a different environment, but the client.
You are testing the service, right?

<?php

namespace App\Http {
    interface HttpClientInterface {}
    class FakeHttpClient implements HttpClientInterface {}
    class RealHttpClient implements HttpClientInterface {}
}

namespace App\Service {
    class ThirdPartyService {
        private $client;
        public function __construct(\App\Http\HttpClientInterface $client) {
            $this->client = $client;
        }
    }
}

?>

// prod
app.http_client:
    class: App\Http\RealHttpClient

// test
app.http_client:
    class: App\Http\FakeHttpClient

app.third_party_service:
    class: App\Service\ThirdPartyService
    arguments: [app.http_client]

Под HttpClientInterface можно подогнать что угодно, от curl до soap, а так же логгировать и собирать данные того что отправляется и что приходит, а так же вешаться на события, которые там же можно и генерить.
Оставьте сервис в покое, пусть делает свою работу.
Так же можете добавить и "псевдореальное окружение" и время от времени запускать тесты с реальными севисами, собирая данные и предохраняясь от внезапного изменения API. Но это другая история.
Если не хотите заморачиватся с клиентом, то замените класс сервиса
// parameters.yml
app.third_party_service_class: App\Service\ThirdPartyService

// config_test.yml
app.third_party_service_class: App\Service\MockThirdPartyService

app.third_party_service:
    class: "%app.third_party_service_class%"

class MockThirdPartyService extends ThirdPartyService {
    public function foo($ignoredArguments) {
        return true;
    }
}

Но в этом случае, если ваш реальный сервис поломается - то увы, тесты будут зеленее травы и врать вам в глаза.

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question