I have a class which has a method that makes use of PHP's global file_get_contents function. I need to test the method on the class, without actually calling the global function.
I know that I could use namespaces to override what gets returned from file_get_contents, however my tests are in a separate namespace already so I cannot simply match namespaces with the class.
Here's some code:
<?php
namespace MyVendor\MyProject;
class MyClass
{
private $someProperty;
public function __construct($override = '')
{
    $this->someProperty = $override;
}
public function myMethod()
{
    $request = 'http://example.com';
    $response = $this->someMethodUsingGlobals($request);
    // Do something with the response..
}
public function someMethodUsingGlobals($url)
{
    return json_decode(file_get_contents($url),true)['results'][0];
}
}
<?php
namespace MyProjectTests;
public function test_it_does_something_with_the_response()
{
    $sut = new MyClass();
    $response = $sut->myMethod();
    $this->assertEquals('Some expectation', $response);
}
I need to mock the someMethodUsingGlobals() method on the class, but not entirely sure how to go about this.
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.
You proceed like this:
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 ];
    }
}
You do the following:
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!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With