PHPUnit testing file_get_contents

You can archive it using a partially Mock Objects: you can mock only a specific method of your class and execute (and tests) the other method.

Further reference here

As example, suppose your modified class:

<?php
namespace Acme\DemoBundle\Service;


class MyClass {

    public function myMethod()
    {
        $request = 'http://domain.com';
        $response = $this->someMethodUsingGlobals($request);
        // Do something with the response..

        return $response;
    }
    public function someMethodUsingGlobals($url)
    {
        return json_decode(file_get_contents($url),true)['results'][0];
    }
}

You can test with the following test class:

<?php
namespace Acme\DemoBundle\Tests;



class MyProjectTest extends \PHPUnit_Framework_TestCase
{

    public function test_it_does_something_with_the_response()
    {
        $sut = $this->getMock('Acme\DemoBundle\Service\MyClass', array('someMethodUsingGlobals') );

        // Set up the expectation for the someMethodUsingGlobals() method
        // to be called only once and with the string 'http://domain.com'
        // as its parameter.
        $sut->expects($this->once())
            ->method('someMethodUsingGlobals')
            ->with($this->equalTo('http://domain.com'))
            ->willReturn('Some expectation');

        $response = $sut->myMethod();


        $this->assertEquals('Some expectation', $response);
    }
}

So the method someMethodUsingGlobals is not executed and return the values defined in the mock deifinition. The method myMethod is executed and processed with the mocked function.

Hope this help


Solution: do a class-wrapper around the native function

The simplest and cleanest way to do it is to create a wrapper class around the native function.

If you follow DDD and/or hexagonal architecture, you'd probably place it in the "adapters" space, and if you do not follow DDD nor hex-arch, place it besides any group of classes that "touch the exterior world".

This wrapper is a one-liner class:

<?php

declare( strict_types = 1 );

namespace MyVendor\MyProject\Adapters;

class FileGetContentsWrapper
{
    public function fileGetContents( string $filename )
    {
        return file_get_contents( $filename );
    }
}

This class cannot be tested, as it just uses the native function.

But with it, you just "shift" the "untesting" to this one-liner class and you now make all the other places that used to use the file_get_contents() testable gaining coverage around teh logic that surrounded the code besides the file reading.

Your original class, modified

You proceed like this:

  1. You inject services via constructor.
  2. You treat the wrapper as a service.
  3. If you are using frameworks like symfony in their recent versions you can use auto-wiring for the injection to simplify the construction of the class.

For example, your class could result in this:

<?php

namespace MyVendor\MyProject;

use MyVendor\MyProject\Adapters\FileGetContentsWrapper;

class MyClass
{
    private $fileGetContentsWrapper;

    public function __construct( FileGetContentsWrapper $fileGetContentsWrapper )
    {
        $this->fileGetContentsWrapper = $fileGetContentsWrapper;
    }

    /* ... */

    public function someMethodUsingTheWrapper( $url )
    {
        $contents = $this->fileGetContents( $url );
        return json_decode( $contents, true )[ 'results' ][ 0 ];
    }
}

How to test it

You do the following:

  1. In a test class, you setup a mock for the wrapper.
  2. Within the test method you configure the mock to return whatever you need.
  3. You invoke your class normally.

For example:

<?php

declare( strict_types = 1 );

namespace MyProjectTests;

use MyVendor\MyProject\Adapters\FileGetContentsWrapper;
use MyVendor\MyProject\MyClass;
use PHPUnit\Framework\TestCase;

class MyClassTest extends TestCase
{
    private $fileGetContentsWrapper;

    //---------------------------------------------------------------------//
    // Setup                                                               //
    //---------------------------------------------------------------------//

    protected function setUp()
    {
        $this->fileGetContentsWrapper = $this->createMock( FileGetContentsWrapper::class )

        parent::setUp();
    }

    //---------------------------------------------------------------------//
    // Tests                                                               //
    //---------------------------------------------------------------------//

    public function testSomeMethodUsingTheWrapper()
    {
        $sut = $this->getSut();

        $someSimulatedJson = '{"results":["abc","xyz"]}';
        $this->fileGetContentsWrapper->method( 'fileGetContents' )->willReturn( $someSimulatedJson );

        $this->assertEquals( 'xyz', $sut->someMethodUsingGlobals( 'dummy-url' ) );
    }

    //---------------------------------------------------------------------//
    // Private                                                             //
    //---------------------------------------------------------------------//

    private function getSut() : MyClass
    {
        return new MyClass( $this->fileGetContentsWrapper );
    }
}

That's all! Hope to help!