I have the following code in PHP:
$response = $this->client->request('GET', $path, $requestBody, $headers);
$isRequestSuccess = $response->getStatusCode() === "200";
if ($isRequestSuccess) {
return $response->getBody()->getContents();
}
It seems like I was successful in creating a mock for the request:
$mockResponse = \Mockery::mock('GuzzleHttp\Psr7\Response');
$clientMock
->shouldReceive('request')
->once()
->withAnyArgs()
->andReturn($mockResponse);
$clientMock->shouldReceive('getStatusCode')->andReturn(200);
But, how should I use Mockery to mock getStatusCode?
It should return a Psr7\Response object of GuzzleHttp.
I know that the $clientMock return value should be assigned to a parameter, but how should I mock the
$response->getStatusCode();
and
$response->getBody()->getContents()
If I'm going to mock the getStatusCode and return 200, I get the following error:
Method Mockery_4_GuzzleHttp_Psr7_Response::getStatusCode() does not exist on this mock object
It is not a $request, it is a $response, you better name it so. It Is very confusing that $request variable contains a response object.
Anyway,
Mockery::mock(ResponseInterface::class)->shouldReceive('getStatusCode')->andReturn(200);
Looking at it deeper, you probably dont have to care, that response Is mocked and useless to test, you would be testing if you set up the mock right, rather then testing your code.
Related
I test code with PHPUnit 9.0.
I use Laravel framework 8.* and PHP 7.4
I struggle to test a function that uses request()
Here is a very short version of the code I have to test:
trait SomeTrait
{
function someFunction()
{
//1. retrieve only the documents
$documents = request()->only('documents');
....
//set an array called $header
$header = [ 'Accept-Encoding' => 'application/json'];
//2. add to Array $header if someKey is available in headers
if (request()->headers->has('someKey'))
{
$header = Arr::add($header, 'someKey', request()->header('someKey'));
}
}
}
At first (1.) it has to get the documents from a request. I solved this with an mock of the request and it works:
$requestMock = Mockery::mock(Request::class)
->makePartial()
->shouldReceive('only')
->with('documents')
->andReturn($document_data);
app()->instance('request', $requestMock->getMock());
$this->someFunction();
I create a mock of request class, that returns $document_data when request()->only('documents'); is called in someFunction().
But then the code request()->headers->has('someKey') returns the error:
Call to a member function has() on null
Can anybody help and explain how I can test the code?
Thanks for the help! I found a solution without mocking the request - sometimes it's easier than you think :D
//create a request
$request = new Request();
//replace the empty request with an array
$request->replace(['documents' => $all_documents]);
//replace the empty request header with an array
$request->headers->replace(['someKey' => 'someValue']);
//bind the request
app()->instance('request', $request);
I run my project and i get this error:
Trying to get property 'headers' of non-object in
"\vendor\laravel\framework\src\Illuminate\Foundation\Http\Middleware\VerifyCsrfToken.php"
in this function
protected function addCookieToResponse($request, $response)
{
$config = config('session');
$response->headers->setCookie(
new Cookie(
'XSRF-TOKEN', $request->session()->token(), $this->availableAt(60 * $config['lifetime']),
$config['path'], $config['domain'], $config['secure'], false, false, $config['same_site'] ?? null
)
);
return $response;
}
What are you actually passing as $response argument in this case?
I believe one needs more details to come up with the concrete cause of why you are getting the error, however I can try to give a hint:
For some reason what you pass in place of $response is not being recognized as an instance of an object in your case.
You could try instantiating it like so:
$response = Response::make($contents, $statusCode);
$response->header('Content-Type', $value);
and then pass it to your function.
Maybe you can try and trace back based on this assumption, where exactly in your Project the chain is broken?
I mean, in your case it is a parameter in your function, but how would it know that it inherits from the Symfony\Component\HttpFoundation\Response class?
Maybe you should "typehint" it -> like say Response $response in the brackets.
I found this link here to be useful explanation also. In the post they talk about Request and not Response, but I think the principle of the issue is is related:
https://www.quora.com/What-does-Request-request-mean-in-Laravel
You can check out the documentation:
https://laravel.com/docs/4.2/responses
I have a function that looks like:
public function downloadProjectFolder($projectId, $taskToken){
// Download the project directory if it isn't on the server
if(is_dir('/path/to/folder/') === false){
$manager = $this->instantiateS3TransferObject($projectId, $taskToken);
$promise = $manager->promise();
$promise->wait();
}
else{
return 'Project Folder Already Exists';
}
}
The above method downloads a folder onto my server from AWS S3 if it doesn't already exist on the local machine. The actual S3 Transfer object (from the AWS PHP SDK V3 library - which in itself is mostly abstracted from Guzzle PHP) is instantiated by the below function:
private function instantiateS3TransferObject($projectId, $taskToken){
$lastDatetime = time();
return new \Aws\S3\Transfer($this->myS3ClientObject, 's3://mys3bucket/destination/url',
'/path/to/local/directory/', array(
'base_dir' => 'destination/url',
'before' => function()use($projectId, $taskToken, &$lastDatetime){
$currentDatetime = time();
if(($currentDatetime - $lastDatetime) >= 30){
$postParams = array(
'project_id' => $projectId,
'task_token' => $taskToken
);
$this->curl->post($postParams, 'http://url/endpoint');
$lastDatetime = $currentDatetime;
}
}
)
);
}
The above essentially starts my folder download and hits an custom endpoint every 30 seconds asynchronously.
How would I mock out the \Aws\S3\Transfer object in this case so that it includes the promise() method on return and that method in turn returns the wait() method?
Not much you can do about the time since it is a native function and cannot be mocked. You can slightly refactor it for the sake of testability to something like:
class TimeGetter
{
public function getTime()
{
return time();
}
}
and then use as
$currentDatetime = $this->timeGetter->getTime();
// instead of $currentDatetime = time();
So you can mock it later, and return whatever time you need to test your functionality.
Neither you can create a mock for \Aws\S3\Transfer, since you explicitly create a new instance in instantiateS3TransferObject.
For the rest of the code you will need to mock both Guzzle and curl. The very rough approximation based on the code snippet in the question:
// First Guzzle, but order doesn't matter:
$mock = new MockHandler([
// first expected request
function($_self, RequestInterface $request, array $options) {
// assert $request values meet expectations
// and return response or exception you expect from S3
return new Response(200, ['X-Foo' => 'Bar']);
},
// second expected request, if any, is either instance of Response, Exception, or a function which returns one of them
// third expected request, etc...
]);
$handler = HandlerStack::create($mock);
// then pass it to the class under test
// assuming it is public:
$cut->myS3ClientObject = new Client(['handler' => $handler]);
// Now curl:
$curlMock = $this->getMockBuilder('\whatever\curl\class\you\use')
->disableOriginalConstructor()
->setMethods(['post'])
->getMock();
$curlMock
->expects($this->once()) // twice, exact, etc ...
->method('post')
->with(
$this->equalTo(['project_id' => 'expected_project_id', 'task_token' => 'expected_token' ]),
$this->equalTo('http://url/endpoint')
);
//again, assuming it is public
$cut->curl = $curlMock;
// then actually execute the method you are testing:
$cut-> downloadProjectFolder('expected_project_id', 'expected_token');
You can read more how to test Guzzle in official docs.
I am trying to assign a value to a variable inside the first testing function and then use it in other testing functions inside the class.
right now in my code the second function fails due to this error:
1) ApiAdTest::testApiAd_postedAdCreated
GuzzleHttp\Exception\ClientException: Client error: 404
and i dont know why. this is how the code looks like:
class ApiAdTest extends PHPUnit_Framework_TestCase
{
protected $adId;
private static $base_url = 'http://10.0.0.38/adserver/src/public/';
private static $path = 'api/ad/';
//start of expected flow
public function testApiAd_postAd()
{
$client = new Client(['base_uri' => self::$base_url]);
$response = $client->post(self::$path, ['form_params' => [
'name' => 'bellow content - guzzle testing'
]]);
$data = json_decode($response->getBody());
$this->adId = $data->id;
$code = $response->getStatusCode();
$this->assertEquals($code, 200);
}
public function testApiAd_postedAdCreated()
{
$client = new Client(['base_uri' => self::$base_url]);
$response = $client->get(self::$path.$this->adId);
$code = $response->getStatusCode();
$data = json_decode($response->getBody());
$this->assertEquals($code, 200);
$this->assertEquals($data->id, $this->adId);
$this->assertEquals($data->name, 'bellow content - guzzle testing');
}
in the phpunit doumintation https://phpunit.de/manual/current/en/fixtures.html i see i can define a
a variable inside the setUp method and then use it as i want but in my case i only know the value after the first post executes. any idea how can i use $this->adId in the second function??
Unit tests by definition should not rely on one another. You will end up with unstable and fragile tests which are then hard to debug the moment they start failing, since the cause is in another test case.
There is no guarantee in which order the tests execute in PHPUnit by default.
PHPUnit supports the #depends annotation to achieve what you want, the docs have the same warning though.
I'm going to write some Symfony2 UnitTests (derived from Symfony\ Bundle\ FrameworkBundle\ Test\ WebTestCase) to test ajax controllers, similar to this How to get Ajax post request by symfony2 Controller.
My big problem is to get the parameters into the "request" bag of the request, not into the "parameter" bag. Similar to the upper example the method in the controller looks like this:
public function ajaxAction(Request $request)
{
$data = $request->request->get('data');
}
But if i do a var_dump of the $request, the paramaters i supply in the WebTestCase do not appear in $request->request, but in $request->parameter. Let's say this is the portion of code in my webtestcase:
....
$client = static::createClient();
$client->request('POST', '/ajax/blahblah', ... ?????);
I already tried supplying the parameter(s) directly within the url as
/ajax/blahblah?data=whocares
I tried specifying the parameter within an array
$client->request('POST', '/ajax/blahblah', array('data' => 'fruityloops'));
But nothing worked. Any chance to get this running?
Thanks in advance
Hennes
After you make the request, you need to get the response. Try this:
$client = static::createClient();
$client->request('POST', '/ajax/blahblah', array('data' => 'fruityloops'));
$response = $client->getResponse();
$this->assertEquals(200, $response->getStatusCode());
//convert to array
$data = json_decode($response->getContent(true), true);
var_dump($data);
$this->assertArrayHasKey('your_key', $data);