How to Test Abstract PHP Classes With PHPUnit

How to Test Abstract PHP Classes With PHPUnit

How do you test abstract classes in PHP? 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 BaseReaderentirely 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 firm 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.

Want to Learn More About Mezzio?

Mezzio Essentials teaches you the fundamentals of PHP's Mezzio framework. It's a practical, hands-on approach, which shows you just enough of about the underlying principles and concepts before stepping you through the process of creating an application.

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 usually 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.


You might also be interested in these tutorials too...

A Short Introduction to Postman
Fri, Jan 10, 2020

A Short Introduction to Postman

Debugging requests can be a time-consuming process. However, there’s a tool that makes doing so a lot simpler. It’s called Postman. In this tutorial, I step you through its core features and show you how to use them.

How to Run Tests in PhpStorm
Fri, Jan 3, 2020

How to Run Tests in PhpStorm

PhpStorm offers so much functionality. From syntax highlighting to Docker integration, it’s an extremely comprehensive tool. However, have you ever thought of using it to run your unit tests? In this article, I step you through running tests, from an entire suite to an individual test.


Want more tutorials like this?

If so, enter your email address in the field below and click subscribe.

You can unsubscribe at any time by clicking the link in the footer of the emails you'll receive. Here's my privacy policy, if you'd like to know more. I use Mailchimp to send emails. You can learn more about their privacy practices here.

Join the discussion

comments powered by Disqus