search

Home  >  Q&A  >  body text

In PHPUnit, how to mock a function that is not part of a class?

The project I'm currently working on contains a mix of object-oriented and procedural PHP code. So I have something like this:

function doStuff($value)
{
    $x = $value + 1;

    return $x;
}

class MyClass
{
    private $field;

    public function setMyValue($amount)
    {
        $this->field = doStuff($amount) + doStuff(2 * $amount);
    }
}

There are several of these dependencies, but very few (you can count them on one hand). However, I need to write a unit test for a class (using PHPUnit) and I don't know how to mock a function from the terminal (in this case doStuff). As far as I know, the mocking functionality in PHPUnit only works with classes.

I would have done it without any mocking, but the problem is that some of these functions do some IO operations; I don't think it's a good idea to not mock them in some way.

How can I solve this problem?

P粉216807924P粉216807924425 days ago715

reply all(2)I'll reply

  • P粉275883973

    P粉2758839732023-10-30 09:33:53

    The only option I see is dependency injection, since your class wants to use resources outside the class. So this breaks some encapsulation rules.

    How I've done this in the past is to put these functions in their own class and require/include them, and when setting the test variables, include a base file with the same semi-"mocked" functions, The file returns to a known state.

    My other approach is to create a simple UTILITY class that contains all these data functions and then use dependency injection and mocking to test it.

    class Utilities
    {
    
        function doStuff($value)
        {
            $x = $value + 1;
            return $x;
        }
    }
    
    class MyClass
    {
        private $UtilitiesObject;
    
        private $field;
    
        public function setMyValue($amount)
        {
    //        $this->field = doStuff($amount) + doStuff(2 * $amount);
            $this->field = $this->UtilitiesObject->doStuff($amount) + $this->UtilitiesObject->doStuff(2 * $amount);
        }
    }
    
        // Constructor Injection, pass the Utilities object here
        public function __construct($Utilities = NULL)
        {
            if(! is_null($Utilities) )
            {
                if($Utilities instanceof Utilities)
                {
                    $this->SetUtilities($Utilities);
                }
            }
        }
    
        function SetUtilities(Utilities $Utilities)
        {
            $this->UtilitiesObject = $Utilities
        }
    
    }

    test:

    class UtilitiesTest extends PHPUnit_Framework_TestCase
    {
    
        // Could also use dataProvider to send different returnValues, and then check with Asserts.
        public function testSetMyValue()
        {
            // Create a mock for the Utilities class,
            // only mock the doStuff() method.
            $MockUtilities = $this->getMock('Utilities', array('doStuff'));
    
            // Set up the expectation for the doStuff() method 
            $MockUtilities->expects($this->any())
                        ->method('doStuff')
                        ->will($this->returnValue(1));
    
            // Create Test Object - Pass our Mock as the Utilities
            $TestClass = new MyClass($MockUtilities);
            // Or
            // $TestClass = new MyClass();
            // $TestClass->SetUtilitiess($MockUtilities);
    
            // Test doStuff
            $amount = 10;   // Could be checked with the Mock functions
            $this->assertEquals(2, $TestClass->doStuff($amount));       // Mock always returns 1, so 1+1=2
        }
    }

    reply
    0
  • P粉155832941

    P粉1558329412023-10-30 00:12:07

    You can take advantage of PHP's namespace fallback policy when you call functions from a namespace (defined in the global namespace) and always call them unqualified functions.

    This allows you to create mocks by providing functions within the caller's namespace.

    To make your life extra easy, I packaged it into a library php-mock- phpunit that can be used with PHPUnit:

    namespace foo;
    
    use phpmock\phpunit\PHPMock;
    
    class BuiltinTest extends \PHPUnit_Framework_TestCase
    {
    
        use PHPMock;
    
        public function testTime()
        {
            $time = $this->getFunctionMock(__NAMESPACE__, "time");
            $time->expects($this->once())->willReturn(3);
    
            $this->assertEquals(3, time());
        }
    }

    reply
    0
  • Cancelreply