erik404
erik404

Reputation: 615

I can't figure out how to PHPUnit test Ratchet/Sockets

Let me first confess that I absolutely am a novice in the field of TDD and unit tests. Currently, I am learning to work with TDD to advance myself in my career.

I am reprogramming a program I wrote a year ago using TDD. The program is not that complex. It listens to a websocket (Ratchet), receives information, parses the information, and sends messages through a message bus (RabbitMQ).

I am trying to approach this via TDD so I am starting to write my test first.

My listener class has 4 methods.

The problems I encounter while writing the test first.

I find it really hard to find information on this specific subject and I understand that my question is broad. I do not expect a complete solution or answer but hopefully, the terminology to further investigate this subject and lectures/tutorials about writing tests for methods of this kind.

Upvotes: 3

Views: 308

Answers (1)

Philip Weinke
Philip Weinke

Reputation: 1844

It's hard to argue without having seen any code. From your description it sounds like you have one class that does all the things. This may already be a reason that makes testing harder than it has to be.

When it comes to unit tests, I like to stick with Michael Feathers definition:

A test is not a unit test if:

  • it talks to the database.
  • it communicates across the network.
  • it touches the file system.
  • it can't run at the same time as any of your other unit tests.
  • you have to do special things to your environment (such as editing config files) to run it.

Mocking the Ratchet and RabbitMQ connections may seem like a way to make a test fit the above definition, but there is also this other phrase: "Don't mock what you don't own". Although it's not a hard rule, I think it's a pretty good guideline. There are a lot of articles on that topic. This should give you a good overview

Ratchet is a bit special with the server and event loop part, though. But it's well tested, so you don't have to do it. My suggestion: Keep the integration with Ratchet so thin, that there isn't much left to test anyway and just skip the test.

final class MyRatchetApp implements MessageComponentInterface
{
    private MessageProcessor $processor;

    public function __construct(MessageProcessor $processor)
    {
        $this->processor = $processor;
    }

    public function onMessage(ConnectionInterface $from, $msg)
    {
        $this->processor->handle($msg);
    }
    
    // ...
}

This leaves you with a MessageProcessor that you can test in isolation. All of its dependencies are types you own, so you can use mocks, stubs or fake implementations to test their interaction. This implementation is overly simplified and misses stuff like error handling that you certainly want to do and of course want to test.

final class MessageProcessor
{
    private MessageParser $parser;

    private MessageBroadcaster $broadcaster;

    public function __construct(MessageParser $parser, MessageBroadcaster $broadcaster)
    {
        $this->parser      = $parser;
        $this->broadcaster = $broadcaster;
    }

    public function handle(string $rawMessage): void
    {
        $this->broadcaster->send($this->parser->parse($rawMessage));
    }
}

interface MessageParser
{
    /**
     * @throws BadMessageException
     */
    public function parse(string $message): Message;
}

interface MessageBroadcaster
{
    /**
     * @throws UnsupportedMessageException
     * @throws UnroutableMessageException
     */
    public function send(Message $message): void;
}

Creating an implementation of the MessageParser should be straightforward and easily unit testable. The RabbitMQ implementation of the MessageBroadcaster would be a perfect candidate for an integration test.

Finally, plug all the concrete implementations together for the real application.

$server = IoServer::factory(
    new MyRatchetApp(
        new MessageProcessor(
            new CommandMessageParser(),
            new RabbitMqMessageBroadcaster()
        )
    ),
    8080
);

To make sure all parts work together, you can create some end-to-end tests that perform full roundtrips and verify the results. Creating these tests first, allows you to do Double Loop TDD

Upvotes: 2

Related Questions