6

I have a very simple class like this one below:

abstract class Person
{
    private $id;
    private $createdOn;

    // ... More private properties

    protected $unfound = array();

The constructor does a foreach on the passed array $data and, using the correct methods, assigns values to properties. If the method doesn't exist, then the key is added to a protected array to keep a trace of it (i called it $unfound, just to be original!).

    public function __construct($data)
    {
        foreach ($data as $field => $value)
        {
            $method = 'set' . ucfirst($field);

            if (method_exists($this, $method))
            {
                $this->$method($value);
            }
            else
            {
                $this->unfound[] = $field;
            }
        }
    }

The list of methods to set the values for properties

    public function setId($id) {
        $this->id = $id;
    }

    public function setCreatedOn($createdOn) {
        $this->createdOn = $createdOn;
    }

And a list of methods to get those assigned values

    public function getId() {
        return $this->id;
    }

    public function getCreatedOn() {
        return $this->createdOn;
    }
} // END of the class

As you can see, the class doesn't do any complicated task: it accepts an array like

array(
    'id' => 4,
    'createdOn' => '2015-01-07 20:50:00',
    'unknownVar' => 'mah'
    // ... Other properties to set
    );

So the class cycles through the array and use the key to call the correct method to set the value. Nothing complex i think.

More complex is, instead, testing it.

As it is an abstract class, i cannot instantiate it directly, but i have to mock it.

My problem is that i can't pass to constructor the correct parameters to test if the values assignment is correctly done.

I've tried to use something like:

public function testPerson()
{
   $abstractClass = '\My\Namespace\Person';

    $testData = array(
        'id' => 1,
        'createdOn' => '2015-01-07 19:52:00',
        'unfound' => 'Inexistent method'
        );

   $methods = array(
      'getId',
      'setId'
      );

    $mock = $this->getMockBuilder($abstractClass)
        ->setConstructorArgs(array($testData))
        ->setMethods($methods)
        ->getMockForAbstractClass();

    $this->assertEquals($testData['id'], $mock->getId());
}

In testPerson(), the $methods variable doesn't contain all methods i need, but for the testing of the test (please, excuse me for the play with words! :) ) i think they are sufficient.

But PHPUnit tells me that:

Failed asserting that null matches expected 1.

It seems like the constructor isn't called, also if the code coverage tells me that methods are called.

is there anyone who can help me understand what is happening and how can i test this class?

Thank you!

Aerendir
  • 6,152
  • 9
  • 55
  • 108
  • Don't test abstract classes! Test the concrete classes that extend those abstract classes. – Mark Baker Jan 07 '15 at 20:21
  • Yes, ok, i know the rule. But i have to test it. Furthermore, it's now a challenge for me! I have to understand the WHY! – Aerendir Jan 07 '15 at 20:23
  • OK, I'll rephrase..... you __can't__ test an abstract class, because (by definition) it can't be instantiated. You can __only__ test the concrete classes that extend it – Mark Baker Jan 07 '15 at 20:24
  • 1
    Eh eh, excuse me, but there is also a method to test abstract classes: getMockForAbstractClass(). I think it's possible (and i've done it in more simple classes). – Aerendir Jan 07 '15 at 20:25
  • 1
    Perhaps http://stackoverflow.com/questions/190295/testing-abstract-classes can help – Mark Baker Jan 07 '15 at 20:34
  • I've read it... Thank you... Anyway, while waiting for replies, i've tried i simple print_r($mock) e saw that the properties were correctly set. The only one that weren't set was $id. While writing with you, i done some other tests... Thanks to the link you posted and that i read again i found where the error was: $methods! I stubbed concrete methods, so they didn't work. As i done my assertTrue only on the first value (id) and as i stubbed the corresponding methods, the tests returned (obviously and correctly!) false: the two values aren't equals. So, the solution was pretty simple... – Aerendir Jan 07 '15 at 20:42
  • Well you've taught me that I should read up more on some of the lesser-known methods within PHPUnit; so the question was beneficial – Mark Baker Jan 07 '15 at 21:27

1 Answers1

3

The solution is very simple: deleting the $methods variable and the calling to setMethods($methods) solved the problem!

Calling setMethods(), in fact, "stubs" those methods that, without setting a proper fixed value, are set to null (the value I received from the tests result).

The method I tested was stubbed whereas the others not.

So a print_r($mock) revealed that other values were correctly set.

So simple but so hard to find! Thank you anyway to you to make me think, so I solved the problem and asked the question!

Paul T. Rawkeen
  • 3,994
  • 3
  • 35
  • 51
Aerendir
  • 6,152
  • 9
  • 55
  • 108