@tukreb

Как правильно проводить функциональное тестирование, если всё завязано через сторонний API/SOAP сервер?

Я занялся идеей, написать функциональные тесты (хотя, сейчас оглядываясь она выглядит глупой) у меня возникли некоторые «сложности», так как почти каждая сущность связана со сторонним запросом. Как правильно замокать сервисы, чтобы это не стало муторным делом?
Например, есть такая сущность, в которой происходит определённая проверка
#[ORM\Table(name: "cp_enterprise_dispatchers")]
#[ORM\Entity]
class EnterpriseDispatcher implements AggregateRoot
{
   #[ORM\Id]
    #[ORM\Column(name: "id", type: "integer", nullable: false)]
    #[ORM\GeneratedValue(strategy: "IDENTITY")]
    private int $id;
    //...остальные поля...
    public function __construct(
        EnterpriseDispatcherServiceInterface $service, string $name, string $url, string $login, string $password, bool $enabled = true
    )
    {
        $this->name = $name;
        $this->url = $url;
        $this->login = $login;
        $this->password = $password;
        $this->solidcpLoginId = $service->getEnterpriseDispatcherRealUserId($url, $login, $password);
        $this->enabled = $enabled;
        $this->solidcpServers = new ArrayCollection();
        $this->recordEvent(new Event\EnterpriseDispatcherCreated($this));
    }
   //гетеры,
}

Сам сервис, который общается с другим сервером.
final readonly class EnterpriseDispatcherService implements EnterpriseDispatcherServiceInterface
{
    public function __construct(
        private EsUsers $esUsers
    ) {}

    public function getEnterpriseDispatcherRealUserId(string $url, string $login, string $password): int
    {
        $this->esUsers->initManual($url, $login, $password);
        try {
            $result = $this->esUsers->getUserByUsername($login);
        } catch (\Exception $e) {
            throw new \DomainException("Soap execution error (Code: {$e->getCode()}, Message: {$e->getMessage()})", $e->getCode(), $e);
        }
        if ($result['IsPeer']) {
            throw new \DomainException("This Login {$login} is Peer. Please use a real User, not Peer");
        }
        return (int)$result['UserId'];
    }
}

Чтобы его замокать, и при этом Симфони не ругался:
Symfony\Component\DependencyInjection\Exception\InvalidArgumentException: 
The "App\Service\Service" service is already initialized, you cannot replace it.

Мне приходиться делать мок класс:
final class EnterpriseDispatcherServiceMock implements EnterpriseDispatcherServiceInterface
{
    public function getEnterpriseDispatcherRealUserId(string $url, string $login, string $password): int
    {
        return 1001;
    }
}

В некоторых таких моках, я добавляю возможность динамически подменять значения, чтобы имитировать разные ответы стороннего сервиса.
После я добавляю в services_test.yaml соответствующую запись:
services:
  _defaults:
    autowire: true

  App\Service\EnterpriseDispatcherService:
    public: true
    class: App\Tests\Mock\EnterpriseDispatcherServiceMock

В итоге код разрастается просто катастрофически, если мне нужно протестировать 20 сервисов, мне нужно создать 20 интерфейсов, 20 мок классов и указать 20 записей в services_test.yaml.
Есть ли проще решения или функциональные тесты — это кроличья нора, в которую лучше не лезь?
  • Вопрос задан
  • 126 просмотров
Пригласить эксперта
Ответы на вопрос 1
AshBlade
@AshBlade
Просто хочу быть счастливым
1. То что ты сейчас сделал называется инверсия зависимостей. В данном случае, ты для каждого внешнего сервиса выделил интерфейс и дополнительно к нему реализацию.
Когда идешь таким путем, то да - получается много компонентов (в данном случае классов/файлов).

2. У меня вопрос к разделению ответственности: зачем в свою сущность передать логин и пароль? (Далее, из предположений пишу) Причем, это пароль именно для сервисной шины, которую ты мокаешь.
Для этого случая, предлагаю вынести все зависимости шины именно в класс реализующий интерфейс этой шины.
В частности:
- login и password (возможно url) станут полями EnterpriseDispatcherService - реализации твоего интерфейса шины
- эти поля из сущности уйдут, т.к. это просто инфраструктурные вопросы и к функциональности (которую надо протестировать) не имеют отношения

P.S. Под функциональным ты имеешь ввиду юнит-тестирование (скорее всего)
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы