Mocking php internal functions in unit tests

At some point, it is necessary to replace calls to an internal php function by a stub or mock which can be controlled by the test, e.g. to return certain error codes. Doing this will overwrite the php function for all tests that follow, so use it with care and only when all other options cannot be used. The following example shows how it is done, by replacing the php function file_get_contents.

<?php

// The namespace must be identical to that of MailManUpdater.php for the function replacement of get_file_contents to
// work
namespace App\Service;

use App\Entity\MailingList;
use App\Tests\Entity\EntityTestHelper;
use PHPUnit\Framework\TestCase;

/**
 * Replace the php get_file_contents function by our own function to be able to define its behaviour. This will only
 * work in our current namespace (i.e. App\Service). The call to this function is passed to the
 * file_get_contents function of the test class.
 *
 * @param mixed $filename
 * @param bool  $use_include_path
 * @param null  $context
 * @param int   $offset
 * @param null  $maxlen
 *
 * @return mixed
 */
function file_get_contents($filename, $use_include_path = false, $context = null, $offset = 0, $maxlen = null)
{
    return MailManUpdaterTest::file_get_contents($filename);
}

/**
 * Class MailManUpdaterTest
 */
class MailManUpdaterTest extends TestCase
{
    /** @var string|bool */
    public static $fileGetContentsReturnValue;
    /** @var string */
    public static $fileGetContentsUrl;

    /**
     * When the php file_get_contents function is called anywhere within the current namespace, the call is redirected
     * to this method. The call just stores the call parameter (so it can be checked later on). It returns a value,
     * which is previously set in a global variable.
     *
     * @param string $filename
     *
     * @return mixed
     */
    public static function file_get_contents($filename)
    {
        self::$fileGetContentsUrl = $filename;

        return self::$fileGetContentsReturnValue;
    }

    /**
     * Setup the test environment, i.e. load fixture data.
     */
    public static function setUpBeforeClass()
    {
        EntityTestHelper::initializeDoctrineEnvironment(true);
    }


    /**
     * Test adding and removing email addresses.
     */
    public function testAddRemoveEmailAddress()
    {
        $entityManager = EntityTestHelper::getEntityManager();
        /** @var MailingList $mailingList */
        $mailingList = $entityManager->getRepository(MailingList::class)
                                     ->findOneBy(['listName' => 'MailingList_1']);
        // In the default fixtures, the domain address is an email, This is changed here for this test.
        $mailingList->setManagementEmail('http://lists.test.com');

        /** @var MailingListUpdateInterface $mailManUpdater */
        $mailManUpdater = new MailManUpdater($entityManager);

        // First add an email address to the list
        self::$fileGetContentsReturnValue = 'Successfully subscribed me@mail.com';
        self::$fileGetContentsUrl = '';
        $mailManUpdater->addEmailAddress($mailingList, 'me@mail.com');
        $expected  = 'http://lists.test.com/mailman/admin/MailingList_1/members/';
        $expected .= 'add?subscribe_or_invite=0&subscribees=me@mail.com&adminpw=password1';
        $this->assertEquals($expected, self::$fileGetContentsUrl);
    }
}

Here is how it works:

Line Explanation
 5 The namespace of the unit test mus tbe identical to that of the class which is tested. Only by doing that, the php function that will be called is diverted to to the one we define here.
24 Write the exact function name and parameters as the one we want to replace.
26 Call OUR function or method which WE can controll within the test environment.
35  Value we want a call to file_get_contents to return. This value is set in the test BEFORE it is called by the object we are testing.
37 Value which is passed to file_get_contents in the object we are testing. AFTER the call we can check if the passed value was correct.
48 Static method which is called when the test object calls file_get_contents.
80 Value we want file_get_contents to return in the following call.
81 Clear the value of the parameter to be passed.
82 Do the actual call to the test object.
85

Check if the parameter passed to file_get_contents is the one we are expecting.

 When you look at the current code of this test in the repository, you will notice that I do not use this way of mocking in this test anymore. The problem was, that a completely different test running in the same namespace was messed up because of the replacement of the file_get_contents function. Therefor I decided to add a special method to the class to be tested to be able to pass a mock method in for the test. Usually I do not like to change the interface of a class just to be able to test it, but sometimes there is no other way to do this.