I'm putting together a set of models for use in a Zend Framework 2 application. So, each model has a table class which acts as the interface between the model and database for querying. Using the ZF2 TableGateway
means that even a fairly simple query, like getting the number of hits from a page log between two dates, ends up with some complex calls:
public function getDailyHits($start, $end)
{
$select = $this->tableGateway->getSql()->select();
$select->columns(array(
"date" => new Expression("DATE_FORMAT(timestamp, '%Y-%m-%d')"),
"hits" => new Expression("COUNT(timestamp)"),
));
$select->where->between("timestamp", $start, $end);
$select->group("date");
$results = $this->tableGateway->selectWith($select);
$dailyHits = array();
foreach ($results as $result) {
$dailyHits[$result->date] = $result->hits;
}
return $dailyHits;
}
When it came to developing the unit test, I wrote up a bunch of expectations:
public function testGetDailySearches()
{
// Setup
$result = new \stdClass();
$result->date = "2015-01-01";
$result->searches = 42;
$resultSet = array($result);
$sql = $this->getMockBuilder("Zend\Db\Sql\Sql")
->disableOriginalConstructor()
->getMock();
$select = $this->getMockBuilder("Zend\Db\Sql\Select")
->disableOriginalConstructor()
->getMock();
$where = $this->getMockBuilder("Zend\Db\Sql\Where")
->disableOriginalConstructor()
->getMock();
$tableGateway = $this->getMockBuilder("Zend\Db\TableGateway\TableGateway")
->disableOriginalConstructor()
->getMock();
$searchLogTable = $this->getMockBuilder("Usage\Model\Table\SearchLog")
->setConstructorArgs(array($tableGateway))
->setMethods(null)
->getMock();
// Expectations
$tableGateway->expects($this->once())
->method("getSql")
->will($this->returnValue($sql));
$tableGateway->expects($this->once())
->method("selectWith")
->with($this->equalTo($select))
->will($this->returnValue($resultSet));
$sql->expects($this->once())
->method("select")
->will($this->returnValue($select));
$select->expects($this->once())
->method("columns")
->will($this->returnValue($select)); // TODO: need to assert parameters
$select->expects($this->once())
->method("__get")
->with($this->equalTo("where"))
->will($this->returnValue($where));
$where->expects($this->once())
->method("between")
->with(
$this->equalTo("time"),
$this->equalTo("2015-10-01 00:00:00"),
$this->equalTo("2015-10-07 23:59:59")
)
->will($this->returnValue($select));
$select->expects($this->once())
->method("group")
->with($this->equalTo("date"));
// Assertions
$this->assertEquals(
array("2015-01-01" => 42),
$searchLogTable->getDailySearches("2015-10-01 00:00:00", "2015-10-07 23:59:59")
);
}
I had a go at using Prophecy to reduce the verbosity in these tests, but got tripped up on them magic __get
method needed to read the $where property from the Select object.
After reading through a couple of threads on the Prophecy site, I uncovered a link to That's Not Yours. It makes a lot of sense. It also would reduce a lot of boilerplate in some tests if I just subclass TableGateway (or even maybe just mock it) so that all the Zend\Db behaviour is retained.
But, then what happens to my expectations? Is this a case of overengineered testing? Should I have these expectations, or should I rely on testing a mostly black box of a method?
If I did remove those expectations, and relied on a mocked result from the subclassed/mocked selectWith, I could write a passing test which didn't specify any of the select criteria, and would fail to function as required in the real world.