Testing YouTube API v3 with PHP and Symfony

This is a little post about how I got some tests up and running on the YouTube v3 API in a Symfony application.

This post also applies to Symfony since I’m creating an angularjs app that will use Symfony as its back-end API. In turn, this will call a load of data providers including YouTube. For my unit tests, I needed to mock the YouTube google services’ search method. For functional tests in Symfony I needed to use the DIC to inject my test YouTube classes.

To add the YouTube service to a symfony install, it can be added to the composer.json file and then referenced from any class (here are the official installation instructions).

//composer.json
"require": {
"google/apiclient": "1.0.*@beta"
...

Unit tests

To unit test the search functionality, this is an extract of my YouTubeProviderTest.php file…

//1
$this->gsYouTube = $this->getMockBuilder('\Google_Service_YouTube')
 ->disableOriginalConstructor()
 ->getMock();
  
//2      
$this->gsYouTube->search = $this->getMockBuilder('\Google_Service_YouTube_Search_Resource')
 ->disableOriginalConstructor()
 ->setMethods(array(
  'listSearch'
 ))
 ->getMock();

//3
$this->gsYouTube->search->expects($this->any())
 ->method('listSearch')
 ->will($this->throwException(new \Exception()));

//4
$emptyFile = 'sampleEmptyYouTubeListings.txt';
$this->gsYouTube->search->expects($this->any())
 ->method('listSearch')
 ->will($this->returnValue(json_decode(file_get_contents($emptyFile),true)));
  1.  The first bit mocks the actual YouTube service, disabling the original constructor. If this isn’t done, a valid Google_Client object needs to be passed to it.
  2. The Google_Service_YouTube object contains a public ‘search’ property, which itself is an instance of the ‘Google_Service_YouTube_Search_Resource’ class. Since its public, its pretty easy to set this to a mock object as well. Its a class with only 1 method, namely, ‘listSearch.’
  3. This statement says that whenever the ‘listSearch’ method is called, throw an exception.
  4. This statement says that whenever ‘listSearch’ is called, return the file ‘sampleEmptyYouTubeListings.txt’ which is a simple json string.

Functional testing

To set up the DIC such that the test environment will automatically use a test version of the YouTube service, I did the following…

All my data providers are themselves inside a service called ‘MediaProviderApi.’ For the YouTube service, inside the ‘services.xml’ file, I’ve got the following…

<!-- 1. google services-->
<service id="google_client" class="%google_provider.class%" public="false">
 <call method="setDeveloperKey">
   <argument>%google_provider.gdata_key%</argument>
 </call>
 <call method="setApplicationName">
   <argument>%google_provider.gdata_app_name%</argument>
 </call>
</service>
        
<!-- 2. youtube services-->
<service id="google_service_youtube" class="%youtube_provider.google_service_youtube.class%" public="false">
 <argument type="service" id="google_client" />
</service>

<!-- 3. youtube provider api -->        
<service id="youtube_provider" class="%youtube_provider.class%" public="false">
 <argument type="service" on-invalid="ignore" id="google_service_youtube" />
 <argument id="sonata.cache.apc" type="service"  />
</service>
  1. The Google_Client service can be constructed here. By parameterising the class, its also possible to pass a test class in here too, although I’ve not needed to do that. Notice the ‘call’ statements. To create a valid Google_Client object, the google data key and application name has to be set. The values above are injected from either the config.yml or config_test.yml
  2. To construct a valid Google_Service_YouTube object, a valid Google_Client must be passed to it. This is done here. By marking the service as ‘public=false’, it means it can’t be directly accessed through the container object. Since the class is parameterised, a reference to a test class can be made from the config_test.yml file.
  3. Finally, the actual youtube provider service is created using the google_service_youtube service. My provider also uses sonata’s APC cache service but that’s beside the point 😉

Service configuration

To make things a bit cleaner, in the configuration.php for the service, I add a default value to the YouTube service class. This means that its only the test class that needs to be defined when necessary.

//DependencyInjection/Configuration.php

//...
->arrayNode('youtube_provider')
  ->isRequired()
  ->children()
    ->arrayNode('google_service_youtube')
    ->addDefaultsIfNotSet()
      ->children()
        ->scalarNode('class')
        ->defaultValue('Google_Service_YouTube')
        ->end()
      ->end()
    ->end()
 ->end()    
->end()//end of youtubeapi

So, in the config.yml file, its only required to define the YouTubeProvider like so…

sk_media_api:
  media_provider_api:
    debug_mode: false
    providers: 
      google_provider:
        gdata_key: %gdata_key%
        gdata_app_name: %gdata_app_name%
      youtube_provider: ~

In config_test.yml, the test class is defined

sk_media_api:
  media_provider_api:
    debug_mode: true
    providers: 
      youtube_provider:
        google_service_youtube: 
          class: Sk\MediaApiBundle\Tests\MediaProviderApi\TestGoogleServiceYouTube

So, in the functional tests, the DI container will load the test google youtube service class so that my YouTube quota isn’t immediately used up.

Hope that helps someone…

Leave a Reply