How to fake a resourse for a unit test in PHP?
Asked Answered
N

3

5

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);
    }
}
Nananne answered 23/12, 2016 at 11:41 Comment(3)
I'm not sure you are going about this the right way. If I've understood you correctly, you're effectively wanting to test PHP built in methods (fwrite, feof etc) against a mocked/fake resource object returned from $resource->openConnection(). You should consider instead to have a wrapper object around those methods, so that you can test $resource->write('something') instead of fwrite($resource, 'something').Theomorphic
Thanks for your comment! No, for sure, I'm not going not test native functions. And, yes, I've create the methods, that wrap the connection setting related PHP functions. But now I want to mock their result for testing of the barIntrefaceMethod(), that calls these wrapping functions. The problem is only, that I don't find any way to fake the resource. That frustrates mocking of the openConnection().Nananne
I know you won't test the native functions, but you have to call them in your code and they are dependent on a real resource handle. The easiest and better designed way of doing this is to have an object wrapped around the native functions, and mock that instead. I shall post an answer to explain what I mean better in code.Theomorphic
T
6

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.

Theomorphic answered 23/12, 2016 at 15:25 Comment(1)
Why not $this->assertSame("some data", $fooClass->barInterfaceMethod()); is better i guess?Gilstrap
W
3

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.

Walleyed answered 23/12, 2016 at 12:7 Comment(0)
C
1

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+');
Confutation answered 22/9, 2023 at 15:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.