Matt S
Matt S

Reputation: 15394

How to write unit tests throwing PDOException

I have a class which handles a variety of database exceptions such as deadlocks and serialized transaction failures. I'm trying to unit test it and ran into a roadblock. An example of the code I want to test:

public function run(callable $callable)
{
    $this->beginTransaction();
    try {
        $callable();
        $this->commit();
    } catch (\PDOException $e) {
        if ($e->getCode() == '40P01' || $e->getCode() == '55P03') {
            // Deadlock (40P01) or lock not available (55P03).
            ...
        } 
        ...
    }
}

The documentation for PDOException says developers should not throw it themselves, and today I found out why when trying to write a unit test:

$obj->run(function() {
    ...
    throw new \PDOException('Deadlock', '40P01');
});

A non well formed numeric value encountered. PDOException breaks the contract with Exception because exception codes must be int and PDOException creates string codes to match SQL standards. They should have made a separate SQLCode property but instead reused the built-in code. Therefore it's impossible to throw a PDOException with a real SQL error code within a unit test.

Next I tried to mock PDOException:

class PDOExceptionMock extends \PDOException
{
    protected $code = '';

    public function setCode($code)
    {
        $this->code = $code;
    }

    // This won't work because getCode is final
    public function getCode()
    {
        return $this->code;
    }
}

This won't compile because 'Cannot override final method Exception->getCode()'.

Since I can't (and don't want to) recreate every type of deadlock and transaction error using a real database, how can one write unit tests which need to throw PDOExceptions?

Upvotes: 1

Views: 858

Answers (1)

Alex Blex
Alex Blex

Reputation: 37128

You don't need to overwrite getCode. It is already there, so you need just to set the code. E.g.

class A {
    public function run(callable $callable)
    {
        try {
            $callable();
        } catch (\PDOException $e) {
            if ($e->getCode() == '40P01' || $e->getCode() == '55P03') {
                return 'expected';
            } 
            return 'unexpected';
        }
        return 'no caught';
    }
}

class StubException extends \PDOException {
    public function __construct() {
        parent::__construct();
        $this->code = '40P01';
    }
}

$a = new A;

echo $a->run(function(){throw new StubException;});

echoes 'expected';

Upvotes: 2

Related Questions