Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to unit test calls to Google API

I have a following method, which retrieves top visited pages from Google Analytics:

public function getData($limit = 10)
{
    $ids = '12345';
    $dateFrom = '2011-01-01';
    $dateTo = date('Y-m-d');

    // Google Analytics credentials
    $mail = 'my_mail';
    $pass = 'my_pass';

    $clientLogin = Zend_Gdata_ClientLogin::getHttpClient($mail, $pass, "analytics");
    $client = new Zend_Gdata($clientLogin);

    $reportURL = 'https://www.google.com/analytics/feeds/data?';

    $params = array(
        'ids' => 'ga:' . $ids,
        'dimensions' => 'ga:pagePath,ga:pageTitle',
        'metrics' => 'ga:visitors',
        'sort' => '-ga:visitors',
        'start-date' => $dateFrom,
        'end-date' => $dateTo,
        'max-results' => $limit
    );

    $query = http_build_query($params, '');
    $reportURL .= $query;

    $results = $client->getFeed($reportURL);

    $xml = $results->getXML();
    Zend_Feed::lookupNamespace('default');
    $feed = new Zend_Feed_Atom(null, $xml);

    $top = array();
    foreach ($feed as $entry) {
        $page['visitors'] = (int) $entry->metric->getDOM()->getAttribute('value');
        $page['url'] = $entry->dimension[0]->getDOM()->getAttribute('value');
        $page['title'] = $entry->dimension[1]->getDOM()->getAttribute('value');
        $top[] = $page;
    }

    return $top;
}

It needs some refactoring for sure, but the question is:

  • How would you write PHPUnit tests for this method?
like image 504
takeshin Avatar asked Jan 31 '11 11:01

takeshin


2 Answers

As I understand it, typically you would want to inject the dependency (the Google client object) into the System Under Test (SUT, the class containing the getData() method).

I always see the experts use constructor injection - and I'm sure it's a better approach as it clearly identifies the dependencies right up front. But, to tell the truth, I can never seem to design my objects well enough to always make that work. So I end up doing with setter injection.

Something like this:

public function getClient()
{
    if (null === $this->_client){
        // $mail and $pass are stored somewhere, right?
        $clientLogin = Zend_Gdata_ClientLogin::getHttpClient($mail, $pass, "analytics");
        $this->_client = new Zend_Gdata($clientLogin);
    }
    return $this->_client;
}

public function setClient($client)
{
    $this->_client = $client;
    return $this;
}

Then in the unit test, you create a $client object as a mock of your live $client, setting up the expectations, and then inject it into your SUT using the setClient($client) method described above.

See what I mean?

like image 179
David Weinraub Avatar answered Sep 26 '22 11:09

David Weinraub


David Weinraub gave you the first half (how to set up your class to be mockable), so I'll address the second half (how to build the mock).

PHPUnit provides a great mocking facility with a simple API. Passing the user and password is too simple to test in my book, so I'd mock just the handling of the query and results. This requires mocks for Zend_Gdata and Zend_Gdata_App_Feed.

public function testGetData() {
    // expected input to and output from mocks
    $url = 'https://www.google.com/analytics/feeds/data?ids=ga:12345...';
    $xml = <<<XML
<feed>
    ...
</feed>
XML;
    // setup the mocks and method expectations
    $client = $this->getMock('Zend_Gdata', array('getFeed'));
    $feed = $this->getMock('Zend_Gdata_App_Feed', array('getXML'));
    $client->expects($this->once())
           ->method('getFeed')
           ->with($url)
           ->will($this->returnValue($feed));
    $feed->expects($this->once())
         ->method('getXML')
         ->will($this->returnValue($xml));
    // create the report (SUT) and call the method being tested
    $report = new MyReport();
    $report->setClient($client);
    $top = $report->getData();
    // check the final output; mocks are verified automatically
    $this->assertEquals(10, count($top));
    $this->assertEquals(array(
            'visitors' => 123, 
            'url' => 'http://...', 
            'title' => 'My Home Page'
        ), $top[0]);
}

The above will test that the URL was correct and return the XML feed expected from Google. It removes all dependence on the Zend_Gdata classes. If you don't use type hinting on setClient(), you can even use stdClass as the base for the two mocks since you will only be using mocked methods.

like image 41
David Harkness Avatar answered Sep 22 '22 11:09

David Harkness