How to Test Abstract PHP Classes With PHPUnit

How do you test abstract classes in PHP? If you've been wondering how, find out how to test them in this post, by using PHPUnit's getMockForAbstractClass method.

Recently, as I was working refactoring some code in a personal project I've been working on, I decided to change one of the classes into an abstract class. The code isn't all the complex. It converts CSV export files from several European banks (okay, currently two) into YNAB's import file format, which is itself a CSV file.

I wrote a class for each bank to read the respective bank's export CSV file. Both of them extend a base reader class, called BaseReader — entirely original, no? It contains several functions which the bank-specific classes:

  1. Shouldn't need to re-implement; and
  2. Can build upon as they increase in specificity.

Here's the source:

abstract class BaseReader implements \Countable, BankAccountReader
{
    protected $data = [];

    protected $reader;

    public function __construct($filename)
    {
        if (is_string($filename)) {
            $this->reader = $this->initialiseFromFilename($filename);
        }

        if (is_resource($filename)) {
            $this->reader = Reader::createFromStream($filename);
        }

        $this->reader->setHeaderOffset(static::HEADER_OFFSET);
    }

    public function count() : int
    {
        return sizeof($this->data);
    }

    public function getBankAccountRecords() : array
    {
        return $this->data;
    }

    public function initialiseFromFilename(string $filename): AbstractCsv
    {
        if (!file_exists($filename)) {
            throw new InvalidFileException('File does not exist');
        }

        if (!is_readable($filename)) {
            throw new InvalidFileException('File is not readable');
        }

        if (filesize($filename) === 0) {
            throw new InvalidFileException('File is empty');
        }

        return Reader::createFromPath($filename, 'r');
    }
}

Given that, it made sense to me to refactor BaseReader to be an abstract class. This, for a little while, lead to what seemed like a stumbling block: How would I test an abstract class with PHPUnit?

From what I knew of PHPUnit, up until that time at least, you couldn't use it to mock abstract classes. Consequently, after I refactored it to be an abstract class, its functionality couldn't be tested any longer.

As I'm a strong believer in only writing testable code, it seemed like I'd have to revert the refactor and find another approach, such as extracting the methods into a trait.

Thankfully, it turns out that PHPUnit can test abstract classes after all. After a little bit of searching I came across PHPUnit's getMockForAbstractClass method.

If you have a read through the method's documentation, you'll see that it says:

The getMockForAbstractClass() method returns a mock object for an abstract class. All abstract methods of the given abstract class are mocked. This allows for testing the concrete methods of an abstract class.

To use it, you don't have to do much. The following example shows all it takes.

$stub = $this->getMockForAbstractClass(
    BaseReader::class,
    [$this->root->url() . '/files/empty.csv']
);

The first argument is the class that you want to mock. The second is a list of arguments to its constructor. After that, you can set up any of the expected methods, their arguments, and return values, as you normally would, along with any assertions.

In Conclusion

And that's how to test abstract classes in PHP, using PHPUnit. There's not a lot to it, but it's still helpful to know, if and when the time comes, to test abstract classes in your code.

Have you used the method previously? Have you had any issues with it? I'd love to hear in the comments.

Further Reading

P.S. If you need assistance on any of your PHP projects, I am available for hire for freelance work.


Matthew Setter. Ethical Hacker, Online Privacy Advocate, and a Software Engineer.

Matthew Setter

Software Engineer, Ethical Hacker, & Online Privacy Advocate.

Matthew Setter is a software engineer, ethical hacker, privacy advocate, & technical writer, who loves travelling. He is based in Nuremberg, Germany. When he's not doing all things tech, he's spending time with his family, and friends.