I have a class that is all about doing HTTP requests, and logging (in file system & database). It's only using 3 dependencies to do these things, so I'm fine with the code so far.
Here it is for convenience :
<?php
namespace App\Services\Pivotal\Request;
use App\Core\Request\HttpRequest;
use App\Core\Request\Log\RequestsLog;
use App\Services\Pivotal\Token\Factory\TokenFactory;
use Config;
class PivotalRequest
{
private $httpRequest;
private $tokenFactory;
private $requestLog;
private $serviceId;
private $baseUri = 'https://api.pivotal.io/vnz';
private $version = 'v1';
public function __construct(HttpRequest $httpRequest, TokenFactory $tokenFactory, RequestsLog $requestLog)
{
$this->serviceId = 287;
$this->httpRequest = $httpRequest;
$this->tokenFactory = $tokenFactory;
$this->httpRequest->setHeaders(
[
"Content-Type" => "application/json",
"Authorization" => "Bearer {$this->tokenFactory->getTokenId()}"
]
);
$this->httpRequest->setLogName('pivotal');
$this->requestLog = $requestLog;
}
public function get($uri, $datas = [])
{
return $this->request($uri, $datas, 'get');
}
public function post($uri, $datas = [])
{
return $this->request($uri, $datas, 'post');
}
public function useVersionNumber($versionNumber)
{
$this->checkVersionNumber($versionNumber);
$this->version = "v{$versionNumber}";
}
protected function request($uri, $datas, $method)
{
$this->prepareRequest($uri, $datas);
return $this->getResponse($method);
}
protected function prepareRequest($uri, $datas)
{
$fullUri = $this->getFullUri($uri);
$this->httpRequest->setRequestLog($this->requestLog->fromConnection($this->serviceId, $fullUri));
$this->httpRequest->setUri($fullUri);
$this->httpRequest->setDatas($datas);
}
protected function getResponse($method)
{
if ($method === 'get') {
$response = $this->httpRequest->get();
}
else if ($method === 'post') {
$response = $this->httpRequest->post();
}
return $response;
}
protected function checkVersionNumber($versionNumber)
{
if (!is_int($versionNumber)) {
throw new \InvalidArgumentException('Version number should be a positive int.');
}
}
private function getFullUri($uri)
{
$this->checkUri($uri);
return "{$this->baseUri}/{$this->version}/$uri";
}
private function checkUri($uri)
{
if (empty($uri)) {
throw new \InvalidArgumentException('Uri should not be empty.');
}
}
}
Now I'd like to write tests for this class. It seems I can't do anything else but mock, mock, and mock even more.
So far, this is what I have :
<?php
use App\Core\Request\HttpRequest;
use App\Core\Request\Log\RequestsLog;
use App\Services\Pivotal\Request\PivotalRequest;
use App\Services\Pivotal\Token\Factory\TokenFactory;
class PivotalRequestTest extends TestCase
{
/**
* @test
*/
public function full_passing_request_test()
{
$tokenId = 1;
$tokenFactoryMock = Mockery::mock(TokenFactory::class);
$tokenFactoryMock->shouldReceive('getTokenId')->once()->withNoArgs()->andReturn($tokenId);
$fullUri = 'https://api.pivotal.io/vnz/v1/livecheck';
$httpRequestMock = Mockery::mock(HttpRequest::class);
$httpRequestMock->shouldReceive('setHeaders')->once()->with([
'Content-Type' => 'application/json',
'Authorization' => 'Bearer 1'
]);
$httpRequestMock->shouldReceive('setLogName')->with('pivotal');
$httpRequestMock->shouldReceive('setRequestLog')->once();
$httpRequestMock->shouldReceive('setUri')->once()->with($fullUri);
$httpRequestMock->shouldReceive('setDatas')->once()->with([]);
$httpRequestMock->shouldReceive('get')->once()->withNoArgs();
$requestLog = new RequestsLog();
$requestLog->srv_name = 'srv_name';
$requestLog->request_name = $fullUri;
$requestLog->ip_src = 'ip_src';
$requestLog->service_id = 287;
$requestLog->request_status = 'prepared';
$requestLog->webservice_status = 'prepared';
$requestLogMock = Mockery::mock(RequestsLog::class);
$requestLogMock->shouldReceive('fromConnection')
->once()
->with(287, $fullUri)
->andReturn($requestLog);
$pivotalRequest = new PivotalRequest($httpRequestMock, $tokenFactoryMock, $requestLogMock);
$pivotalRequest->get('livecheck', []);
}
}
This looks really smelly to me, because I feel like I'm only describing the inners of the class, so instead of describing what the class should do, I describe how it does what it does (which should be of no interest, right ?).
Is that common to have so many mocks (basically almost only mocks) to test a class ? Is that a good practice ? If not, how can I change this ?