Basic Zend Framework Testing With Mockery

Do you need to mock objects in your Zend Framework 2 applications and find PHPUnit unintuitive, even difficult? If so, come and learn Mockery instead.


Testing should be an integral part of your Zend Framework 2 development process. Ok, it should be an integral part of any development process. The trouble is, when it comes to mocks and stubs, PHPUnit’s Mock Objects doesn’t have the most intuitive interface.

I’ve been trying to learn the PHPUnit approach for some time now, but always found it cumbersome and unintuitive. However, after talking with colleagues I found an alternative - it’s called Mockery.

Honesty Note: The theory of mocks, stubs, and fakes is something which has taken me a little while to get my head around so that I can fully appreciate the approach.

Padraic Brady, the package’s author, describes it as:

Mockery is a simple yet flexible PHP mock object framework for use in unit testing with PHPUnit, PHPSpec or any other testing framework. Its core goal is to offer a test double framework with a succinct API capable of clearly defining all possible object operations and interactions using a human readable Domain Specific Language (DSL). Designed as a drop in alternative to PHPUnit’s phpunit-mock-objects library, Mockery is easy to integrate with PHPUnit and can operate alongside phpunit-mock-objects without the World ending.

Note: It’s not an either/or approach. You can use the rest of PHPUnit as normal for testing Zend Framework 2 applications, but swap out the mock objects component in favour of Mockery.

From spending time learning Mockery over the last month, I’ve found it to be both simpler and more intuitive. So in this two-part series, I’m stepping you, lightly, through how to use Mockery, in combination with PHPUnit, to test both models and controllers.

This isn’t an exhaustive or theoretical analysis of Mockery. It’s a learn by doing covering in only as much detail as is required to follow what’s going on. But there will be a number of links available when you want to go further.

Assumptions

  1. Zend Framework 2 application built on Zend Framework Skeleton
  2. PHPUnit setup in at least one model based on the standard setup

Testing Models

The underlying model class in this post is based on the Getting Started guide in the Zend Framework 2 manual (version 2.2). It’s available in this gist. The class there, features sufficient functionality to:

  • Create and update records
  • Retrieve a record object
  • Fetch all objects
  • Delete an object

Our tests will consist of the following:

  • A record cannot be deleted without supplying a record id
  • Can create a new record
  • Can update an existing record
  • Can search all records

That should be enough for a starter post I believe. You can find the complete test class here in this gistTweet me if you want to see more as I’m happy to keep on adding them.

Installing Mockery

But first we need to add mockery support in our project. To do that, under the require section in composer.json add the following:

"mockery/mockery": "dev-master"

Then run composer update. Within about a minute or so, the mockery library will be available in your project under the vendor directory.

Setting up Mockery

To make the integration complete we need one more step. As Mockery evaluates expectations with its close() method, we need a tearDown() method calling \Mockery::close(). Without this, we’ll get false positives.

public function tearDown()

m::close();

Simplifying the Tests

One last piece and we’re ready to test. Each of the tests share two common elements, the class being tested and a mock table gateway object. So in setup, we’ll perform a basic initialisation of them.

public function setup()
$this->_mockTableGateway = \Mockery::mock(
  'Zend\Db\TableGateway\TableGateway'
);
$this->_mockGenericTable = new \Generic\Model\GenericTable(
  $this->_mockTableGateway
);

You can see that it’s very straight-forward with Mockery. No long initialisation, with multiple function parameters. Just specify the class which we’re mocking, Zend\Db\TableGateway\TableGateway and we’re largely setup.

If we had a set of methods which we were always testing, you’d be surprised at how simple it is. Assume that we were only testing select, selectWith and execute. We could have rewritten the above as follows:

$this->_mockTableGateway = \Mockery::mock(
  'Zend\Db\TableGateway\TableGateway[select,selectWith,execute]'
);

Simple, straight-forward, effective. Now we have a mocked connection to the database and our class forming the System Under Test (SUT). With this, we’re ready to go.

Test 1. Can’t delete a record without a record id

Let’s start off simply. We want to ensure that the delete method returns false if an invalid id is passed to it (you could also have thrown an exception).

public function testCannotDeleteRecordWithoutId()
$this->assertEquals(
  false,
  $this->_mockGenericTable->deleteGeneric("")
);

Using the standard PHPUnit assertEquals, we can confirm that this has happened.

Test 2. Can delete a record with a record id

What about deleting a record with a valid id? In this case, we want to check that the function runs through the procedure of deleting a record as we’d expect it to; which is that it calls the delete function once on the Table Gateway object, passing in the id specified.

The delete method returns an integer matching the number of records affected by the operation, so we set it to return the correct number, 1. As before, we then call the method on our table object and use assertEqual to check the returned value.

public function testCanDeleteRecordWithValidId() {
$recordId = 22;
  $recordsDeleted = 1;
  $this->_mockTableGateway
       ->shouldReceive('delete')
       ->times(1)
       ->with(array('id' => 22))
       ->andReturn($recordsDeleted);
  $this->assertEquals(
    $recordsDeleted,
    $this->_mockGenericTable->deleteGeneric($recordId)
  );
}

Test 3. Can create a new record

Now, we want to check that, when passed sufficient information for a new record, but without a unique identifier, that the function would carry out the functions required to do so.

public function testSaveNewRecord() {
$data = new Generic();
  $data->artist = "Matthew Setter";
  $data->title = "Artist";
  $data->id = 0;
  $insertCount = 1;

Here we’ve setup a new model object and specified the number of records we expect to be affected by the operation.

$functionData = array(
    'artist' => $data->artist,
    'title'  => $data->title,
  );

We then setup the object which should be passed to the insert function.

$this->_mockTableGateway
       ->shouldReceive('insert')
       ->times(1)
       ->with($functionData)
       ->andReturn($insertCount);

Here, we mock the TableGateway insert method. It should be called one time, receiving the record details and return a count of one, indicating that the record’s been created.

$this->assertEquals(
    $insertCount,
    $this->_mockGenericTable->saveGeneric($data)
  );

}

Finally, we call assertEquals to check that, when the function’s called on the object with the record data, that it would go interact with the database as expected.

Test 4. Can update an existing record

Now this is largely the same as the previous test, but instead of adding a new record, we’re checking that, by passing in an id, it will execute the update process instead of insert.

public function testUpdateExistingRecord() {
$data = new Generic();
  $updateCount = 1;
  $functionData = array(
    'artist' => "Matthew Setter",
    'title'  => "Artist",
    'id'     => 12
  );
$data->exchangeArray($functionData);

As before, we setup a model object, but with a valid id value.

$mockResultset = \Mockery::mock(
    'Zend\Db\ResultSet\ResultSet[current,count]'
  );

As the function determines if the update was successful, based on a ResultSet object, we prepare to mock the relevant functions: current and count.

$mockResultset->shouldReceive('current')
                ->times(1)
		->andReturn($functionData);

Here we mock a call to the current function, specifying it must be called one time and return an array with the required object data.

$mockResultset->shouldReceive('count')
              ->times(1)
      	      ->andReturn(1);

Here, we mock the count function, specifying it must be called once and return 1, which simulates that the record was retrieved successfully.

$this->_mockTableGateway
       ->shouldReceive('update')
       ->times(1)
       ->with(array(
         'artist' => $functionData['artist'],
         'title'  => $functionData['title']
       ),
       array('id' => $data->id)
     )
     ->andReturn($updateCount);

Now we mock the call to update, specifying that it should be called once with both the record data and the identifying id; finally returning the count of records updated.

$this->_mockTableGateway
       ->shouldReceive('select')
       ->times(1)
       ->with(array('id' => $functionData['id']))
       ->andReturn($mockResultset);

As the function has a sanity check of first looking up the record before attempting to update it, we need to mock that call on the TableGateway object.

We do so, specifying it’s called once, with the id of the record previously set and that it will return the ResultSet object we just mocked.

$this->assertEquals(
    $updateCount,
    $this->_mockGenericTable->saveGeneric($data)
  );

Finally, we use assertEquals to validate that the save method returns the expected count.

Test 5. Can search all records

Ok, we’ve worked through most of the CRUD operations. Now let’s turn to searching records. Let’s look at a function which uses Zend\Db\Sql, and invokes the select and where methods.

public function testFetchSelect() {

Firstly, we setup the array which will be passed to the where function and the expected order clause.

$orderClause = "lastName DESC";
    $whereArguments = array(
        "firstName" => "Michael",
        "lastName" => "Roberts"
    );

Now, we mock the returned ResultSet object.

$resultSet = new ResultSet();

Now we start getting serious, mocking the Zend\Db\Sql object. First we specify that the select method gets called once and returns itself.

$mockSql = \Mockery::mock('Zend\Db\Sql');
    $mockSql->shouldReceive('select')
            ->times(1)
	    ->andReturn($mockSql);

Then, we mock the where method must be called once, that it receives the where array we setup previously and, again, returns itself.

$mockSql->shouldReceive('where')
          ->with($whereArguments)
	  ->times(1)
	  ->andReturn($mockSql);

Then we mock that the order method is called once, receiving the order by clause we previously setup and, again, returns itself.

$mockSql->shouldReceive('order')
          ->with($orderClause)
          ->times(1)
          ->andReturn($mockSql);

We’re just about there. Now we move to mock that the TableGateway object will call both getSql and select; the first returning the mocked mockSql object, and the second returning the ResultSet object.

$mockTableGateway->shouldReceive('getSql')
                   ->andReturn($mockSql);
  $mockTableGateway->shouldReceive('select')
                   ->with($mockSql)
                   ->andReturn($resultSet);

Finally, we use assertEquals to assert that the mocked ResultSet object matches the one returned from the call to the objects function.

$this->assertEquals(
    $resultSet,
    $mockGenericTable->fetchSelect()
  );

Conclusion

There you go, that’s a basic run through of using Mockery instead of PHPUnit’s mock objects to carry out some basic testing of Zend Framework 2 model objects.

Yes, it’s been light on theory and background to both mocking objects and Mockery, but that’s intentional. My focus here was instead to focus on some hands on examples, whilst providing enough links so that you can find more as you’re ready.

Add your thoughts. What would you have done differently? How would you have approached it?


You might also be interested in...


Want more tutorials like this?

If so, enter your email address in the field below and click subscribe.

You can unsubscribe at any time by clicking the link in the footer of the emails you'll receive. Here's my privacy policy, if you'd like to know more. I use Mailchimp to send emails. You can learn more about their privacy practices here.


Join the discussion

comments powered by Disqus