How to fake a resourse for a unit test in PHP?

5.6k views Asked by At

I had a method, that opened a socket connection, used, and then closed it. In order to make it testable, I moved the dealing with the connection to separate methods (see the code below).

Now I want to write a unit test for the barIntrefaceMethod() and need to mock the method openConnection(). With other words, I need a fake resource.

Is it possible / How to "manually" create a variable of the type resource in PHP (in order to fake handles like "opened files, database connections, image canvas areas and the like" etc.)?


FooClass

class FooClass
{
    public function barIntrefaceMethod()
    {
        $connection = $this->openConnection();
        fwrite($connection, 'some data');
        $response = '';
        while (!feof($connection)) {
            $response .= fgets($connection, 128);
        }
        return $response;
        $this->closeConnection($connection);
    }

    protected function openConnection()
    {
        $errno = 0;
        $errstr = null;
        $connection = fsockopen($this->host, $this->port, $errno, $errstr);
        if (!$connection) {
            // TODO Use a specific exception!
            throw new Exception('Connection failed!' . ' ' . $errno . ' ' . $errstr);
        }
        return $connection;
    }

    protected function closeConnection(resource $handle)
    {
        return fclose($handle);
    }
}
3

There are 3 answers

1
John Joseph On

As per my comments, I think you would be better off refactoring your code a little to remove the dependency on native functions being actually called with an actual resource handle. All you care about for the test of the FooClass::barInterfaceMethod is that it returns a response. That class need not concern itself with whether the resource is opened, closed, written to, etc. So that is what should be mocked out.

Below I have rewritten what you have in your question to demonstrate, in some simplistic and non-production pseudo-code:

Your real class:

class FooClass
{
    public function barInterfaceMethod()
    {
        $resource = $this->getResource();
        $resource->open($this->host, $this->port);
        $resource->write('some data');

        $response = '';
        while (($line = $resource->getLine()) !== false) {
            $response .= $line;
        }

        $resource->close();

        return $response;
    }

    // This could be refactored to constructor injection
    // but for simplicity example we will leave at this
    public function getResource()
    {
        return new Resource;
    }
}

Your test of this class:

class FooClassTest
{
    public function testBarInterfaceMethod()
    {
        $resourceMock = $this->createMock(Resource::class);
        $resourceMock
            ->method('getLine')
            ->will($this->onConsecutiveCalls('some data', false)); // break the loop   

        // Refactoring getResource to constructor injection
        // would also get rid of the need to use a mock for FooClass here.
        // We could then do: $fooClass = new FooClass($resourceMock);
        $fooClass = $this->createMock(FooClass::class);
        $fooClass
            ->expects($this->any())
            ->method('getResource');
            ->willReturn($resourceMock);

        $this->assertNotEmpty($fooClass->barInterfaceMethod());
    }
}

For the actual Resource class, you don't need to then unit test those methods that are wrappers around native functions. Example:

class Resource
{
    // We don't need to unit test this, all it does is call a PHP function
    public function write($data)
    {
        fwrite($this->handle, $data);
    }
}

Lastly, the other alternative if you want to keep your code as-is is to setup test fixture resources that you actually connect to for test purposes. Something like

fsockopen($some_test_host, $port);
fopen($some_test_data_file);
// etc.

Where $some_test_host contains some data you can mess with for tests.

0
Timurib On

Resource mocking requires some magic.

Most of I/O functions allows to use a protocol wrappers. This feature used by vfsStream to mock the file system. Unfortunately the network functions such as fsockopen() don't support it.

But you can to override the function. I don't consider the Runkit and APD, you can do it without PHP extensions.

You create a function with same name and custom implementation in the current namespace. This technique is described in another answer.

Also the Go! AOP allows to override any PHP function in most cases. Codeception/AspectMock uses this feature to functions mocking. The Badoo SoftMocks also provides this functionality.

In the your example you can completely override fsockopen() by any method and use vfsStream to call the fgets(), fclose() and other I/O functions. If you don't want to test the openConnection() method you can mock it to setup the vfsStream and return the simple file resource.

0
Martin J. On

You can use the following. It won't be a mock but at least the function which requires a resource won't crash and your unit test will go through.

$resource = fopen('php://temp', 'r+');