Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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);
    }
}
like image 559
automatix Avatar asked Mar 11 '23 08:03

automatix


2 Answers

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.

like image 111
John Joseph Avatar answered Mar 19 '23 18:03

John Joseph


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.

like image 36
Timurib Avatar answered Mar 19 '23 17:03

Timurib