I have a class (PDOMySQLForeignKeysToURLsService
) that does exactly what it says on the tin, it takes a JSON encoded database entity, compares it to a configuration file and transforms any foreign key fields into URLs linking to other RESTful resources. i.e.
[
{
"ID": "1",
"person_id": "1",
"pc_name": "my_computer"
}
]
assuming person_id
is a foreign key in the database, will become something like
[
{
"ID": "1",
"person_id": "http://localhost/api/db/person/1",
"pc_name": "my_computer"
}
]
because this service itself is initalized from a configuration file it can only accept text in it's constructor,
public function __construct(string $pdoDSN, string $username, string $password, string $tablename, string $schemaname)
everything seems to running fine and dandy, but now I want to write some unit tests for this class, and part of it requires that a PDO
object is instantiated and the INFORMATION_SCHEMA
table queried like below
SELECT
*
FROM
INFORMATION_SCHEMA.KEY_COLUMN_USAGE
WHERE
TABLE_SCHEMA = ? AND
CONSTRAINT_NAME != "PRIMARY" AND
REFERENCED_TABLE_NAME IS NOT NULL AND
REFERENCED_COLUMN_NAME IS NOT NULL
The method in which this occurs is in an implementation of an interface Resolvable
, defining function resolveRequest(ServiceRequest $request, ServiceResponse $response)
I basically have no idea how to go around testing all of this besides setting up a schema/some tables in the tests set up? I have heard from my peers that this is bad practice and does not constitute unit testing but rather integration testing - so my question is how could I go about unit testing this method, is unit testing even appropriate here? The class is by definition very dependant on MySQL (and PDO). Should I write some kind of adapter around PDO and use a DI framework to inject it, and interface with that rather than PDO directly? I'm pretty lost at what to do, tbh, and don't understand the idea/how to mock dependencies that are only used within the scope of a method
Even if you can't inject the PDO instance, there are still ways to unit test this - it just requires a little creativity. Probably the easiest way to unit test what you're describing would be to create a testable subclass that allows for injection. The exact details would depend on how your class is implemented, but a simple example might look something like this:
class PDOMySQLForeignKeysToURLsService {
protected $pdo;
public function __construct(string $pdoDSN, string $username, string $password, string $tablename, string $schemaname) {
$this->pdo = $this->createPdo(...);
}
// Encapsulate the PDO creation so that the subclass can override it.
protected function createPdo(...) {
return new PDO(...);
}
// Other methods omitted...
}
class TestablePDOMySQLForeignKeysToURLsService extends PDOMySQLForeignKeysToURLsService {
// Override the constructor so you can pass in a mock PDO as the last parameter
public function __construct(string $pdoDSN, string $username, string $password, string $tablename, string $schemaname, PDO $pdo) {
// Save the injected mock PDO instance.
$this->pdo = $pdo;
parent::__construct(...);
}
// Override the creation so that you can just use the injected mock.
protected function createPdo(...) {
return $this->pdo;
}
}
The TestablePDOMySQLForeignKeysToURLsService
class would live next to your tests and you would use it in your test cases instead of the production class. The idea is that the "testable" class is a minimal extension of the production class - just enough to let you inject test doubles. This lets you test the logic from the production class and mock the database calls in your test without having to change the interface to the production class. It's kind of cheating, but it gets the job done.