rr.
rr.

Reputation: 6644

PHPUnit Mock Objects and Static Methods

I am looking for the best way to go about testing the following static method (specifically using a Doctrine Model):

class Model_User extends Doctrine_Record
{
    public static function create($userData)
    {
        $newUser = new self();
        $newUser->fromArray($userData);
        $newUser->save();
    }
}

Ideally, I would use a mock object to ensure that fromArray (with the supplied user data) and save were called, but that's not possible as the method is static.

Any suggestions?

Upvotes: 68

Views: 116461

Answers (8)

Ankit Raonka
Ankit Raonka

Reputation: 6857

Mockery's Alias functionality can be used to mock public static methods

http://docs.mockery.io/en/latest/reference/creating_test_doubles.html#creating-test-doubles-aliasing

Upvotes: 3

Ilia Yatsenko
Ilia Yatsenko

Reputation: 832

Found the working solution, would to share it despite the topic is old. class_alias can substitute classes which are not autoloaded yet (works only if you use autoloading, not include/require files directly). For example, our code:

class MyClass
{
   public function someAction() {
      StaticHelper::staticAction();
   }
}

Our test:

class MyClassTest 
{
   public function __construct() {
      // works only if StaticHelper is not autoloaded yet!
      class_alias(StaticHelperMock::class, StaticHelper::class);
   }

   public function test_some_action() {
      $sut = new MyClass();
      $myClass->someAction();
   }
}

Our mock:

class StaticHelperMock
{
   public static function staticAction() {
      // here implement the mock logic, e.g return some pre-defined value, etc 
   }
}

This simple solution doesn't need any special libs or extensions.

Upvotes: 6

b01
b01

Reputation: 4384

I would make a new class in the unit test namespace that extends the Model_User and test that. Here's an example:

Original class:

class Model_User extends Doctrine_Record
{
    public static function create($userData)
    {
        $newUser = new self();
        $newUser->fromArray($userData);
        $newUser->save();
    }
}

Mock Class to call in unit test(s):

use \Model_User
class Mock_Model_User extends Model_User
{
    /** \PHPUnit\Framework\TestCase */
    public static $test;

    // This class inherits all the original classes functions.
    // However, you can override the methods and use the $test property
    // to perform some assertions.
}

In your unit test:

use Module_User;
use PHPUnit\Framework\TestCase;

class Model_UserTest extends TestCase
{
    function testCanInitialize()
    {   
        $userDataFixture = []; // Made an assumption user data would be an array.
        $sut = new Mock_Model_User::create($userDataFixture); // calls the parent ::create method, so the real thing.

        $sut::test = $this; // This is just here to show possibilities.

        $this->assertInstanceOf(Model_User::class, $sut);
    }
}

Upvotes: 9

Serhii Vasko
Serhii Vasko

Reputation: 443

One more approach:

class Experiment
{
    public static function getVariant($userId, $experimentName) 
    {
        $experiment = self::loadExperimentJson($experimentName):
        return $userId % 10 > 5;  // some sort of bucketing
    } 

    protected static function loadExperimentJson($experimentName)
    {
        // ... do something
    }
}

In my ExperimentTest.php

class ExperimentTest extends \Experiment
{
    public static function loadExperimentJson($experimentName) 
    {
        return "{
            "name": "TestExperiment",
            "variants": ["a", "b"],
            ... etc
        }"
    }
}

And then I would use it like so:

public function test_Experiment_getVariantForExperiment()
{
    $variant = ExperimentTest::getVariant(123, 'blah');
    $this->assertEquals($variant, 'a');

    $variant = ExperimentTest::getVariant(124, 'blah');
    $this->assertEquals($variant, 'b');
}

Upvotes: -1

Leonid Shagabutdinov
Leonid Shagabutdinov

Reputation: 1110

Another possible approach is with the Moka library:

$modelClass = Moka::mockClass('Model_User', [ 
    'fromArray' => null, 
    'save' => null
]);

$modelClass::create('DATA');
$this->assertEquals(['DATA'], $modelClass::$moka->report('fromArray')[0]);
$this->assertEquals(1, sizeof($modelClass::$moka->report('save')));

Upvotes: 0

Gordon
Gordon

Reputation: 317177

Sebastian Bergmann, the author of PHPUnit, recently had a blog post about Stubbing and Mocking Static Methods. With PHPUnit 3.5 and PHP 5.3 as well as consistent use of late static binding, you can do

$class::staticExpects($this->any())
      ->method('helper')
      ->will($this->returnValue('bar'));

Update: staticExpects is deprecated as of PHPUnit 3.8 and will be removed completely with later versions.

Upvotes: 48

treeface
treeface

Reputation: 13351

There is now the AspectMock library to help with this:

https://github.com/Codeception/AspectMock

$this->assertEquals('users', UserModel::tableName());   
$userModel = test::double('UserModel', ['tableName' => 'my_users']);
$this->assertEquals('my_users', UserModel::tableName());
$userModel->verifyInvoked('tableName'); 

Upvotes: 15

Pascal MARTIN
Pascal MARTIN

Reputation: 401182

Testing static methods is generally considered as a bit hard (as you probably already noticed), especially before PHP 5.3.

Could you not modify your code to not use static a method ? I don't really see why you're using a static method here, in fact ; this could probably be re-written to some non-static code, could it not ?


For instance, could something like this not do the trick :

class Model_User extends Doctrine_Record
{
    public function saveFromArray($userData)
    {
        $this->fromArray($userData);
        $this->save();
    }
}

Not sure what you'll be testing ; but, at least, no static method anymore...

Upvotes: -3

Related Questions