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:
- You inject services via constructor.
- You treat the wrapper as a service.
- 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:
- In a test class, you setup a mock for the wrapper.
- Within the test method you configure the mock to return whatever you need.
- 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!