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.