Static methods themselves aren’t harder to test than instance methods. The trouble arises when a method–static or otherwise–calls other static methods because you cannot isolate the method being tested. Here is a typical example method that can be difficult to test:
public function findUser($id) {
Assert::validIdentifier($id);
Log::debug("Looking for user $id"); // writes to a file
Database::connect(); // needs user, password, database info and a database
return Database::query(...); // needs a user table with data
}
What might you want to test with this method?
- Passing anything other than a positive integer throws
InvalidIdentifierException
. Database::query()
receives the correct identifier.- A matching User is returned when found,
null
when not.
These requirements are simple, but you must also setup logging, connect to a database, load it with data, etc. The Database
class should be solely responsible for testing that it can connect and query. The Log
class should do the same for logging. findUser()
should not have to deal with any of this, but it must because it depends on them.
If instead the method above made calls to instance methods on Database
and Log
instances, the test could pass in mock objects with scripted return values specific to the test at hand.
function testFindUserReturnsNullWhenNotFound() {
$log = $this->getMock('Log'); // ignore all logging calls
$database = $this->getMock('Database', array('connect', 'query');
$database->expects($this->once())->method('connect');
$database->expects($this->once())->method('query')
->with('<query string>', 5)
->will($this->returnValue(null));
$dao = new UserDao($log, $database);
self::assertNull($dao->findUser(5));
}
The above test will fail if findUser()
neglects to call connect()
, passes the wrong value for $id
(5
above), or returns anything other than null
. The beauty is that no database is involved, making the test quick and robust, meaning it won’t fail for reasons unrelated to the test like network failure or bad sample data. It allows you to focus on what really matters: the functionality contained within findUser()
.