PHP单元测试:如何测试外部api的低级功能

Hello folks, I wrote a low level implementation for a XmlRPC-Api and I've trouble to test the communication.

Here is my code.

abstract class BaseClient
{
    protected function call($method, array $params = array())
    {
        $request = xmlrpc_encode_request($method, $parameters);

        $file = file_get_contents($this->getDSN(), false, $context);
        $response = xmlrpc_decode($file);

        if ($response && xmlrpc_is_fault(array($response))) {
            trigger_error("xmlrpc: {$response[faultString]} ({$response[faultCode]})");
        }

        return $response;
    }
}


Client extends BaseClient
{
    public function testCall($msg)
    {
        return $this->call('testMethid', array($msg));
    }
}

And here is my Test.

ClientTest extends PHPUnit_FrameWork_TestCase
{
    public function testTestCall()
    {
        $c = new Client();
        $resp = $c->testCall('Hello World');

        $this->assertEquals('Hello World', $resp);
    }
}

This test will crash every time, because its not possible to access the api inside a testing environment. I can't see a solution to mock and inject the call function. What can I do? Maybe my object structure is bad and not able to test and how can I improve this structure (if this happen)?

Cheers.

Since you're trying to test an external API, I would begin by wrapping your file_get_contents() call in another class and injecting that into your BaseClient. In the simplest form, it might look something like this:

class RemoteFileRetriever
{
    public function retrieveFileContents($url)
    {
        // Do some work to create $context
        ...

        // Now grab the file contents
        $contents = file_get_contents($url, false, $context);

        return $contents;
    }
}

abstract class BaseClient
{
    private $fileRetriever;

    public function __construct(RemoteFileRetriever $fileRetriever)
    {
        $this->fileRetriever = $fileRetriever;
    }

    protected function call($method, array $params = array())
    {
        ...

        $file = $this->fileRetriever->retrieveFileContents($this->getDSN());

        ...
    }
}

Now in your test, you can use a mock object to inject as the file retriever. E.g.:

class ClientTest extends PHPUnit_FrameWork_TestCase
{
    public function testTestCall()
    {
        $mockRetriever = new MockRemoteFileRetriever();
        $c = new Client($mockRetriever);
        $resp = $c->testCall('Hello World');

        $this->assertEquals('Hello World', $resp);
    }
}

PHPUnit atually has some built-in helpers for mocking. See PHPUnit's Mock Objects.

You don't want to mock the call function.

If you can't setup a fake service then you want to mock the php functions which you can do using PHP Namespacing (Have to have PHP 5.3). You can then create mocks for internal php functions that you are calling in your call method.

http://www.schmengler-se.de/-php-mocking-built-in-functions-like-time-in-unit-tests

If you are not able to do this, testing can be pretty difficult. Can you create a fake api that you can hit for testing? Remember that you aren't actually testing the methods of the api, rather you are trying to make sure that you code makes the request to the api and handles the response in the manner you intend.

As a rule, assume that third party code has been tested and works properly.