6

My API code

public function store (Request $request, $profileId)
{
    $all = $request->all();
    $token = AccessToken::with('users')->where('access_token',$request->input('access_token'))->first();

    if($token && $token->users->isOwnProfile($profileId))
    {
        $rules = [
            'access_token'     => 'required',
            'title'            => 'required',
            'description'      => 'required',
            'file_id'          => 'required',
            'audience_control' => 'required|in:' . join(',', PostRepository::$AUDIENCE_CONTROL),
            'tags'             => 'required',
        ];
        $validator = Validator::make($all, $rules);

        $error = $validator->errors()->toArray();
        if ($validator->fails())
        {
            return $this->setStatusCode(401)
                ->setStatusMessage(trans('api.validation failed'))
                ->respondValidationMessage($error);
        }
        try {
            $response = $this->postRepository->save($request, $profileId);
            if(isset($response['error']))
                return $this->messageSet([
                    'message' => $response['error']['message'],
                ], $response['error']['status_code']);

            return $this->setDataType('post_id')
                ->setStatusCode('200')
                ->respondWithCreatedId(trans('api.Post created'), $response->id);
        } catch (\Exception $e) {
            return $this->respondInternalError(trans('api.processing error'));
        }
    }
    return $this->respondInternalError('404 page');

}

From save method it calls another method that calls an external API.

/*
 * this function returns some response where it has profile_id for 
 * the file which in other save function is matched that the
 * profile_id passed as parameter is same with file profile_id
 */
public function getFileDetails($file_id)
{
    try
    {
        $response = json_decode((new Client())->request('GET', env('xyz','http://abc.xyz/api/v1').'/files/' . $file_id)->getBody()->getContents(), true);
    }
    catch (RequestException $e)
    {
        $response = json_decode($e->getResponse()->getBody()->getContents(), true);
    }

    return $response;

}

Now this is my test function for API.

public function testPostCreateChecksProfileMatchesCorrectly()
{
    $this->json('POST', 'profiles/' . $this->getProfile()->id . '/posts' . '?access_token=' . $this->getAccessToken(), [
        'title' => 'api testing title',
        'description' => 'api testing description',
        'audience_control' => 'public',
        'tags' => [
            'Animals',
            'City'
        ],
        'file_id' => '281'
    ])->seeJsonStructure([
        'success' => [
            'message',
            'post_id',
            'status',
        ],
    ]);
}

Now my question is how can I create a fake response for the external API when I am testing.

I am using PHPUnit & Laravel 5.2.

ARIF MAHMUD RANA
  • 5,026
  • 3
  • 31
  • 58

2 Answers2

2

You can use PHP VCR to record the output of the API request.
https://github.com/php-vcr/php-vcr.

With this package, you will be able to save it in the tests/fixtures directory.
So after the first time, PHPUnit will read this file instead of doing other requests.
And in this way will be added also to your GIT repo.

First you need to install it using:

composer require php-vcr/php-vcr 
composer require php-vcr/phpunit-testlistener-vcr 

The second package integrates PHPUnit with PHP-VCR.

Then add to your phpunit.xml after

<listeners>
    <listener class="VCR\PHPUnit\TestListener\VCRTestListener" file="vendor/php-vcr/phpunit-testlistener-vcr/src/VCRTestListener.php" />
</listeners>

Then create the dir called 'fixtures' in the tests directory of your Laravel application.

Now we can test like:

/** @test */
public function itShouldGetVenueGpsCoordinates()
{

    $address = "Milano, viale Abruzzi, 2";
    VCR::turnOn();
    VCR::insertCassette('mapquestapi');
    $coordinates = $this->venueService->getVenueGpsCoordinates($address);

    VCR::eject();
    VCR::turnOff();

    $this->assertEquals(45, number_format($coordinates['lat'], 0));
    $this->assertEquals(9, number_format($coordinates['lng'], 0));
}

The test will create then a fixture, in my case called 'mapquestapi', that you will be able to add to your GIT repo. So from the second time, the API will be called by your test, the data will be loaded from this file instead of making a new request.

If you are subscribed to Laracast you can see a complete video tutorial here. https://laracasts.com/lessons/testing-http-requests

Davide Casiraghi
  • 15,591
  • 9
  • 34
  • 56
-5

First of all never test a thing that you don't own. An external API call is a think that you don't own. There may a thousand of issue or use case that could be go wrong, like your submitting data, network error, or their internal error. Each of the case you should test the situation, which is tedious. Rather use a moc object and put some expectation on that. Listen to this podcast, http://www.fullstackradio.com/37 this will help you to understand clearly.

Try to break your code into small chunk and then test each chunk separately. You are expecting too much from a single test. put your validation logic in a FormRequest class, this will help you a lot.

  • 6
    I'd say "never test a thing that you don't own." is a terrible advice. I always need a set of integration tests to make sure I talk to the 3rd party correctly. It's a small test suite as I always introduce an abstraction for the 3rd party communication, but it exists. The abstraction can be then mocked (but not the interaction with the 3rd party). – Jakub Zalas Mar 31 '16 at 11:36
  • 1
    I agree with @JakubZalas. We always want all correctly work. – Cong LB Aug 03 '18 at 10:21