PHPUnit: Test array of objects

PHPUnit since version 3.1.4 has the assertion "assertContainsOnly" with the parameter "type" which can assert any PHP type (including instances and internal types), and in at least version 3.7 has the assertion "assertContainsOnlyInstancesOf" which explicitly only checks for class instances, not PHP variable types.

So the test checking if an array contains only objects of a given type is simply:

$this->assertContainsOnlyInstancesOf('Student', $students);

Note that this check implicitly tests that $students is either an array or an object implementing the Traversable interface. Implementing Traversable does not mean you can count, so calling assertCount afterwards to assert a given number of Student objects is present is not guaranteed to succeed, but the added check that the return value in fact is an array seems too much bloat to me here. You are creating and returning an array with something in it - it is safe to assume you can count it. But this might not be the case everywhere.


I've written some example code below; I guessed the parameters to getStudents were optional filters. We have one test that gets all students. I don't know if they always come back in sorted order, which is why I don't test anything else in the Student class. The second test gets one particular student, and starts to test some of the Student properties.

class StudentsTest extends PHPUnit_Framework_TestCase{

    public function testGetAllStudents(){
        $s=new Students;
        $students=$s->getStudents("","");
        $this->assertIsArray($students);
        $this->assertEquals(7,count($students));
        $first=$students[0];    //Previous assert tells us this is safe
        $this->assertInstanceOf('Student',$first);
    }

    public function testGetOnlyStudentNamedBob(){
        $s=new Students;
        $students=$s->getStudents("Bob","");
        $this->assertIsArray($students);
        $this->assertEquals(1,count($students));
        $first=$students[0];    //Previous assert tells us this is safe
        $this->assertInstanceOf('Student',$first);
        $this->assertEquals('Bob',$first->getStudentName());
    }
}

This is a good first step. After you use it for a while you'll realize it is quite fragile. I.e. you must have exactly 7 students for the first test to pass. There must be exactly one student called Bob for the second to pass. If your \OldStudents::getStudentByName is getting data from a database, it will also be slow; we want unit tests to run as quick as possible.

The fix for both of those is to mock the call to \OldStudents::getStudentByName. Then you can inject your own artificial data, and then you'll only be testing the logic in getAllStudents. Which in turn means that when your unit test breaks there are only 20 or so lines where you could have broken it, not 1000s.

Exactly how to do the mocking is a whole 'nother question, and could depend on PHP version, and how flexible your code setup is. ("OldStudents" sounds like you are dealing with legacy code and maybe you cannot touch it.)