0

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?

Lloyd Banks
  • 35,740
  • 58
  • 156
  • 248

1 Answers1

1

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.

Alex Blex
  • 34,704
  • 7
  • 48
  • 75