drrcknlsn drrcknlsn - 7 months ago 35
PHP Question

How do I unit test socket code with PHPUnit?

I currently have a

Socket
class, which is basically just an OO wrapper class for PHP's
socket_*
functions:

class Socket {
public function __construct(...) {
$this->_resource = socket_create(...);
}

public function send($data) {
socket_send($this->_resource, $data, ...);
}

...
}


I don't think I can mock the socket resource since I'm using PHP's socket functions, so right now I'm stuck as to how to reliably unit test this class.

Answer

You appear to be missing a small piece to the unit test mindset.

Your problem is easily solvable by creating a Stub object. Strangely, I give this answer time and time again, so it must be largely missed by a lot of people.

Because I see so much confusion as to the differences between stubs and mocks, let me lay it out here, as well...

  • A mock is a class that extends another class that the test is directly dependent on, in order to change behaviors of that class to make testing easier.
  • A stub is a class that implements an API or interface* that a test cannot test easily directly on its own, in order to make testing possible.

^-- That is the clearest description of the two I've ever read; I should put it on my site.

Sockets have this nice feature where you can bind to port 0 for testing purposes (seriously, it's called the "ephemeral port").

So try this:

class ListeningServerStub
{
    protected $client;

    public function listen()
    {
        $sock = socket_create(AF_INET, SOCK_STREAM, 0);

        // Bind the socket to an address/port
        socket_bind($sock, 'localhost', 0) or throw new RuntimeException('Could not bind to address');

        // Start listening for connections
        socket_listen($sock);

        // Accept incoming requests and handle them as child processes.
        $this->client = socket_accept($sock);
    }

    public function read()
    {
        // Read the input from the client – 1024 bytes
        $input = socket_read($client, 1024);
        return $input;
    }
}

Create this object and set it to listen in your test's setUp() and stop listening and destroy it in the tearDown(). Then, in your test, connect to your fake server, get the data back via the read() function, and test that.

If this helps you out a lot, consider giving me a bounty for thinking outside the traditional box ;-)