PackageBase icon indicating copy to clipboard operation
PackageBase copied to clipboard

No way to pass mocked SoapClient in tests

Open vadimonus opened this issue 3 years ago • 5 comments

Trying to write tests for next case. My app is calling soap service and correctly stores response to db. I have many response xml examples from service documentation app, so I use Mockery::mock(\SoapClient::class) to make partial mock and override __doRequest method to return expected xml. But after that i have no way to pass this class to AbstractSoapClientBase descendant. What i see:

  1. Default soap client is defined by constant in SoapClientInterface, so it is not possible to use class, created by mockery, or any anonimous class.
  2. AbstractSoapClientBase::initSoapClient, called by constructor, allways creates default SoapClient, because getSoapClientClassName() is called without params. There is no way to pass $soapClientClassName with options.
  3. If use setSoapClient() to set other implementation of soap client, SoapClient is created two times, first in initSoapClient, second is Mock.
  4. It is possible to override AbstractSoapClientBase::getSoapClientClassName class in tests, but then i need some sort of injection to pass overrided class to application instead of original only in tests. It would be much simplier to do so with configs.

vadimonus avatar May 25 '22 02:05 vadimonus

Can you paste your testing code? Or create a Gist containing the files

mikaelcom avatar May 25 '22 09:05 mikaelcom

Currently i use Laravel container to Mock Serivce

In application

$options = array(
    AbstractSoapClientBase::WSDL_URL => 'https://xxxx?wsdl',
    AbstractSoapClientBase::WSDL_CLASSMAP => ClassMap::get(),
);
$service = app()->make(new Service($options));
$service->setSoapHeaderHeader($header);
if ($send->sendToESP(new ServiceRequest()) !== false) {
    print_r($send->getResult());
} else {
    print_r($send->getLastError());
}

in test

    protected function mockApi($xml): void
    {
        $this->app->bind(Service::class, function ($app, $params) use ($xml) {
            [$options] = $params;
            $soapClientMock = Mockery::mock(SoapClient::class, [
                $options[AbstractSoapClientBase::WSDL_URL],
                [
                    'classmap' => $options[AbstractSoapClientBase::WSDL_CLASSMAP],
                ],
            ]);
            $soapClientMock->makePartial();
            $soapClientMock->shouldReceive('__doRequest')->andReturn($xml);
            $soapClientMock->shouldReceive('__getLastResponse')->andReturn($xml);

            $serviceMock = new Service();
            $serviceMock->setSoapClient($soapClientMock);
            return $serviceMock;
        });
    }

But as you can see, it's very complicated solution.

vadimonus avatar May 26 '22 09:05 vadimonus

I currently don't see a solution to solve your issue. It's working this way and modifying it would myabe break the usage long ago designed as it is. Feel free to propose a way to ease your issue.

mikaelcom avatar May 26 '22 20:05 mikaelcom

One of improvement that i currently can see, is to add config option for class name, and pass this option to getSoapClientClassName in initSoapClient, because currently there is no other way to pass any value to getSoapClientClassName

vadimonus avatar May 27 '22 07:05 vadimonus

Maybe I'm wrong but imagine you:

  • create your own SoapClient class
  • create your own SoapClientBase class from which the ServiceType class would inherit
  • use SoapClientBase as the main class using the --soapclient option when generating the package (if you're actually using the https://github.com/WsdlToPhp/PackageGenerator project)
  • mock the SoapClient class in your test or change its behaviour depending on the current "env" context (prod, dev, test)

mikaelcom avatar Jun 08 '22 14:06 mikaelcom