8

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.

FtDRbwLXw6
  • 27,774
  • 13
  • 70
  • 107
  • As far as I can see the omitted parts `...` are important – KingCrunch Feb 03 '12 at 22:28
  • @KingCrunch: They are important in that they're what I want to test, but unimportant to my question. Because the `socket_*` functions are dependent on socket resources (which AFAIK can't be mocked), I can't reliably test these methods. – FtDRbwLXw6 Feb 03 '12 at 23:23
  • I have some opinions on unit testing networking code in general, but I have zero experience with PHP. If you don't get any replies in a few days from PHP developers, I'll write some comments then. It would be helpful if you posted some code that uses your socket class, as that will likely be factored into my answer. – selbie Feb 04 '12 at 08:12
  • Based upon the response to my answer, I believe this question may be seriously flawed. You should consider greatly revising it if my answer wasn't even in the right ball park. – Theodore R. Smith Feb 06 '12 at 02:47
  • See http://stackoverflow.com/questions/11280506/how-to-mock-built-in-php-socket-functions/11280543#11280543 where I had a similar question and ended up with code that shows how to mock PHP's built-in functions. – Darren Cook Jul 02 '12 at 02:52

1 Answers1

16

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($this->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 ;-)

Community
  • 1
  • 1
Theodore R. Smith
  • 21,848
  • 12
  • 65
  • 91
  • I don't think you understood my question. Maybe it's my fault for not being clear (if so, let me know how I can modify it to be better). The code I want to test ***is*** the server, so stubbing the server would not help me. Nor would stubbing the client, because as I pointed out, even the stub would be dependent on an external resource (much like unit testing filesystem code). – FtDRbwLXw6 Feb 05 '12 at 15:44
  • I guess to make it more clear: what happens if I stub the client, but a firewall is blocking net traffic on the chosen port. Or some PHP setting disallows socket opening. Or we choose a port < 1024 on a system without root. There are a lot of things that could potentially cause the tests to fail when the code is not at fault, and that makes for unreliable testing. – FtDRbwLXw6 Feb 05 '12 at 15:47
  • @drrcknlsn If you want help unit testing a server that's not part of your Socket class, then you're **horribly confused** as to what Unit Testing is about. If you want to test a server, then just switch to stubbing a client (obviously!). If none of the above, not only was the question bad, but I'm totally confused, ugh. – Theodore R. Smith Feb 06 '12 at 02:45
  • and it may not answer your question, but my answer should *definitely* help others with this stuff, so nothing was ultimately lost as long as at least one person benefits. – Theodore R. Smith Feb 06 '12 at 02:46
  • @TheodoreRSmith: Sorry, I don't know how better to clarify the issue. It doesn't matter if I'm testing the server or the client (both use the same class, as my Socket class simply wraps the native `socket_*` functions, which themselves are used for either/or. The problem with stubbing classes like this is that the classes depend on *actual sockets* to work properly. This means that there is a point of failure *outside my code* that could cause the tests to fail. – FtDRbwLXw6 Feb 06 '12 at 06:39
  • Given your example stub, what happens if my network interface hiccups and causes `$this->client` to be closed between the time it's accepted and the time the dependent unit test runs? The test will fail, but not because there is something wrong with my code - rather because of an outside error. This is what I meant by my tests not being reliable. Hope this helps! – FtDRbwLXw6 Feb 06 '12 at 06:44
  • 1
    Um hey man. It's all testing localhost. Your unit test just has to depend on your local network stack not having any serious bugs. – Theodore R. Smith Feb 07 '12 at 03:52
  • I'm accepting this answer, as I don't believe it's possible to completely eliminate outside interference (like network failures) when testing socket code. I suppose it could be theoretically possible if one were to mock the network interface, but that seems a bit impractical for my purposes. – FtDRbwLXw6 Mar 13 '12 at 14:26
  • 2
    Excellent Theodore, I was exactly looking for a solution like that – Antoine 'hashar' Musso Jul 02 '13 at 09:16
  • @TheodoreR.Smith could I ask you how this solution what you provide here can work ? Cause if you will call `socket_accept` the unit test will get stuck untill new connection will come. – Kamil Aug 05 '16 at 10:30
  • Haven't gotten this to work. The `socket_accept()` call is blocking so my unit test won't proceed until a socket connection is established. If I use `socket_set_nonblock()` then `socket_accept()` will return `false` and it still fails. – Thijs Riezebeek Nov 17 '16 at 07:54