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.
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've tried to configure Pact for PHP using example configuration. My problem is I can run a mockServer, but every request I make returns 404 response. Of course I set everything up like in a GitHub readme. Still, I know server is visible (localhost config) but routes could not be registered.
Code example:
class PactTest extends \Tests\BaseTestCases\V2TestCase
{
/** #var MockServerConfig */
private $config;
public function setUp()
{
// Create your basic configuration. The host and port will need to match
// whatever your Http Service will be using to access the providers data.
$this->config = new MockServerConfig();
$this->config->setHost('localhost');
$this->config->setPort(7200);
$this->config->setConsumer('someConsumer');
$this->config->setProvider('someProvider');
$this->config->setHealthCheckTimeout(60);
$this->config->setCors(true);
// Instantiate the mock server object with the config. This can be any
// instance of MockServerConfigInterface.
$server = new MockServer($this->config);
// Create the process.
$server->start();
// Stop the process.
$server->stop();
}
public function testSimple()
{
$matcher = new Matcher();
// Create your expected request from the consumer.
$request = new ConsumerRequest();
$request
->setMethod('GET')
->setPath('/test/abc')
->addHeader('Content-Type', 'application/json');
// Create your expected response from the provider.
$response = new ProviderResponse();
$response
->setStatus(200)
->addHeader('Content-Type', 'application/json;charset=utf-8')
->setBody([
'message' => $matcher->term('Hello, Bob', '(Hello, )[A-Za-z]')
]);
// Create a configuration that reflects the server that was started. You can
// create a custom MockServerConfigInterface if needed. This configuration
// is the same that is used via the PactTestListener and uses environment variables.
$builder = new InteractionBuilder($this->config);
$builder
->given('a thing exists')
->uponReceiving('a get request to /test/abc')
->with($request)
->willRespondWith($response); // This has to be last. This is what makes an API request to the Mock Server to set the interaction.
$service = new HttpClientService($this->config->getBaseUri()); // Pass in the URL to the Mock Server.
$result = $service->getTestAbc(); // Make the real API request against the Mock Server.
$builder->verify();
self::assertEquals('Hello, Bob', $result); // Make your assertions.
}
Where getTestAbc() is:
public function getTestAbc(): string
{
$uri = $this->baseUri;
$response = $this->httpClient->get("{$uri->getHost()}/test/abc", [
'headers' => ['Content-Type' => 'application/json']
]);
$body = $response->getBody();
$object = \json_decode($body);
return $object->message;
}
What do I do wrong?
You're stopping the mock server in setUp. You should stop the server after the test in tearDown. I've noticed that's the code from the manual and it may be quite misleading, but I think it was intended as an example how to start/stop mock server by hand.
Please note that I've also created an issue on the related repo.
In the documentation, it says that this function will return a promise which will resolved after all the promises in the array have resolved.
Here is my implementation;
private function downloadListingImages($contents)
{
$response = [];
$name = 1;
foreach ($contents['images'] as $image) {
$response[] = $this->downloadImage($image, $name);
$name++;
}
return $response;
}
private function downloadImage($link, $name)
{
$guzzle = new Client([
'handler' => HandlerStack::create(new HttpClientAdapter($this->loop)),
]);
$promise = $guzzle->getAsync($link, [
'save_to' => 'assets/' . $name . '.jpg',
]);
return $promise;
}
$promises = $this->downloadListingImages($contents);
Now, everything is fine till this point. But want I want to do is get $contents from a request to my server. So I have a server implementation;
$server = new React\Http\Server(function (Psr\Http\Message\ServerRequestInterface $request) use ($promises) {
\React\Promise\all($promises)->always(function($val) {
file_put_contents('meh.txt', "meh");
});
return new React\Http\Response(
200,
array('Content-Type' => 'text/plain'),
"Hello World!\n"
);
});
What I expect here that $server returns an immediate response (which it does) and after a while see the meh.txt in my repo. However, it never falls to always callback. And even when I don't chain any function on all method, it just resolves itself. Shouldn't it wait until then or something similar to be called to be resolved? How can run my guzzle promises async and get informed when the work is finished?
As long as the objects that passed to \React\Promise\all implement the method then, it works very well. E.g. \React\Promise\all works very fine with with \React\Promise\Promise() and also with \GuzzleHttp\Promise\Promise.
So far I'm not able to reproduce this issue.
As you can see in the documentation the always-method doesn't accept any parameter. Consider to use then instead of always if you in need of a parameter.
Also consider that file_put_contents MAY blocking the event-loop.
I hope this helps.
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.