PHPUnit: Testing The Constructor

In a previous blog post I explained how you could mock the system under test. This allows you to easily verify if a given method calls other methods in the same class.

What if you wanted to verify this from the constructor?

Normal classes

Example Car class
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?php
class Car
{
  /**
  * @var int
  */
  protected $doors;

  /**
  * Class constructor
  *
  * @return void
  */
  public function __construct($doors = 2)
  {
      $this->setDoors($doors);
  }

  /**
  * Setter for the doors
  *
  * @param int $doors
  * @return void
  */
  public function setDoors($doors)
  {
      $this->doors = $doors;
  }
}

The constructor is a magic function in your class and is invoked when you create a new instance of said class. You don’t explicitly call the constructor method.

Being a meticulous developer and trying to reach 100% unit test coverage, I explicitly want to verify if the constructor calls setDoors. One solution would be the following:

Unit test for the Car class constructor
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class CarTest extends PHPUnit_Framework_TestCase
{
  public function testConstructorCallsInternalMethods()
  {
      $classname = 'Car';

      // Get mock, without the constructor being called
      $mock = $this->getMockBuilder($classname)
          ->disableOriginalConstructor()
          ->getMock();

      // set expectations for constructor calls
      $mock->expects($this->once())
          ->method('setDoors')
          ->with(
                  $this->equalTo(4)
                );

      // now call the constructor
      $reflectedClass = new ReflectionClass($classname);
      $constructor = $reflectedClass->getConstructor();
      $constructor->invoke($mock, 4);
  }
}

First thing I do, is create a mock object for the Car class, but I request that the constructor is not called with disableOriginalConstructor. Next I set the expectations for my mock object: verify that the setDoors method is called with 4 as an argument. Lastly, through reflection I get a reflection method for the constructor, which I then invoke for the mock object and correct argument.

Abstract classes

For regular classes, the method described above seems straightforward. When dealing with abstract classes, mocking needs some more attention.

PHPUnit provides us with a getMockForAbstractClass method, which conveniently creates a mock for an abstract class. What’s in a name. Now where the regular getMock method doesn’t mock any method unless you specify the ones you want, getMockForAbstractClass by default mocks the abstract methods so you can test the concrete methods.

So far things are logical. If you try the same approach as above for abstract classes, exchanging the ->getMock() for ->getMockForAbstractClass(), you end up with a disappointment. The mock tells you that the internal method wasn’t called. Odd. Doing some var_dump-driven-development tells you that the method is called. However, it’s not reflected in the mock.

How to fix?

Unit test for abstract Car class
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class CarTest extends PHPUnit_Framework_TestCase
{
  public function testConstructorCallsInternalMethods()
  {
      $classname = 'Car';

      // Get mock, without the constructor being called
      $mock = $this->getMockBuilder($classname)
          ->disableOriginalConstructor()
          ->setMethods(array('setDoors'))
          ->getMockForAbstractClass();

      // set expectations for constructor calls
      $mock->expects($this->once())
          ->method('setDoors')
          ->with(
                  $this->equalTo(4)
                );

      // now call the constructor
      $reflectedClass = new ReflectionClass($classname);
      $constructor = $reflectedClass->getConstructor();
      $constructor->invoke($mock, 4);
  }
}

Tadaa! For an abstract class, we explicitly have to define the list of methods we want to stub.

Come to think of it, this is actually logical behavior, as this is how regular mocking works. What’s not logical, is that it works for normal classes without the list of methods you want to stub. So just to be on the safe side (and to have some kind of pattern), just always provide a list of methods. At least, that’s what I’m going to do from now on.

Comments