PHPUnit: Testing static calls

When you’re writing code, sometimes it’s necessary to use static calls. Maybe the only way to interface with an external library is through static calls. I must admit, it’s just easy to work with a static call. Until you have to assert in a unit test that you’re actually calling the static method. Then it gets more difficult.

Stubbing and mocking internal static methods

As Sebastian Bergmann explains in one of his blogposts, since PHPUnit 3.5 it’s possible to stub & mock static methods. In the same class. Here’s how it goes.

Sample class with internal static method call
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
class Foo
{
  public static function bar()
  {
      // some logic here, and then...
      static::baz($somevar);

      return 'bar';
  }

  public static function baz($somevar)
  {
      // some more logic
  }
}

The unit test goes like this:

Unit test internal static method call
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
class FooTest extends \PHPUnit_Framework_TestCase
{
    public function testBar()
    {
        $class = $this->getMockClass('Foo', array('baz'));

        $class::staticExpects($this->any())
          ->method('baz')
          ->with($this->equalTo('somevar value'));

        $this->assertEquals('bar', $class::bar());
    }
}

So what happens here? The key is that we have 2 static methods, and in the first static method we do a static call to the second static method. All in the same class.

We tell the getMockClass method to give us a mock classname for our Foo class, and we want to stub method baz. Once we have this classname, we can use staticExpects for our expectations.

Then we do our regular static call to the bar method. But instead of doing it on our Foo class, we do it on the mock classname. As such, the mock can use the stub we created when internally static::baz is called.

Stubbing and mocking external static methods

So far that wasn’t too difficult. But how many times have you written code like this? Personally, I don’t ever write code like that. However, sometimes I have to work with an external library and then static calls might be the only available interface.

Sample class with external static method call
1
2
3
4
5
6
7
8
9
10
<?php
class Foo
{
  public function bar()
  {
      // some logic here, and then...

      Log::message('We have just executed Foo::bar()');
  }
}

As you can see, the interface to the Log class makes us use a static call to log a message. Assume that you want to know if you actually do the Log::message call in a unit test, then we’re screwed since the static call is external.

There is a way around that. It’s a hassle and it’s not beautiful. First we have to refactor the external static call a bit:

Sample class with external static method call
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
class Foo
{
  protected $loggingClass = 'Log';

  public function bar()
  {
      // some logic here, and then...

      forward_static_call_array(
          array($this->loggingClass, 'message'),
          array('We have just executed Foo::bar()');
      );
  }
}

Instead of calling Log::mesage directly, we use forward_static_call_array. The name of our log class is now defined in a property, and we use that property in the forward_static_call_array method. Can you see where this is going? We’re kind of injecting the Log classname dependency. Not pretty, but it works!

Unit test external static method call
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
class FooTest extends \PHPUnit_Framework_TestCase
{
    public function testBar()
    {
      // create a mock class with expectations
        $class = $this->getMockClass('Log', array('message'));
        $class::staticExpects($this->once())
          ->method('message');

      $foo = new Foo();

      // set the mock classname in the property
      $reflProp = new \ReflectionProperty('Foo', 'loggingClass');
      $reflProp->setAccessible(true);
      $reflProp->setValue($foo, $class);

      // call bar()
      $foo->bar();
    }
}

Same as before, we use getMockClass to get a mock classname for the Log class. Then we set this classname in our $loggingClass property. Since it’s a protected property, I can only do that through Reflection. I just made the property protected to make it more difficult ;–).

So here we go, we were able to test an external static method call. It’s up to you to decide if you want to refactor your code like this. And it’s also up to you to decide if you want to actually assert these kinds of calls. I agree, we don’t really want to verify a call to a logger. But that was just an example.

Comments