DZone
Web Dev Zone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
  • Refcardz
  • Trend Reports
  • Webinars
  • Zones
  • |
    • Agile
    • AI
    • Big Data
    • Cloud
    • Database
    • DevOps
    • Integration
    • IoT
    • Java
    • Microservices
    • Open Source
    • Performance
    • Security
    • Web Dev
DZone > Web Dev Zone > Practical PHP Testing Patterns: Mock Object

Practical PHP Testing Patterns: Mock Object

Giorgio Sironi user avatar by
Giorgio Sironi
·
Mar. 14, 11 · Web Dev Zone · Interview
Like (0)
Save
Tweet
938 Views

Join the DZone community and get the full member experience.

Join For Free

The Test Doubles we have seen until now are rather passive: they provide predefined results or record calls, but make no decision on their own. A Mock Object instead, is more than that: it verifies if it's used correctly, by making implicit assertions on what you pass it.

Method calls are the communication medium in object-oriented programming: a Mock Object is a Test Double which performs verifications on the calls made on it and their parameters, verifying thus the indirect output of the System Under Test (its side-effects instead of its returned values).

Mocks implement Behavior Verification at its best; Stubs and Mocks are also the most commonly used Test Doubles. Remember that the difference is only that the Mock actively checks what you pass him and how you call it.

Checking parameters and calls is the Mock's job, but it may need to return something to allow the SUT not to fail or explode. Consider the contract and simplify it as most as possible to limit the Mock's complexity: this is another selling point for Test Doubles.

Implementation

Mocks are greatly supported by PHPUnit, which even calls all Test Doubles mocks.

However, PHPUnit but mixes up definition of expectations with definitions of canned results, so it's not really clear when you're just defining a Stub and when you're creating a Mock. Basically, when you're using with(), or will($this->returnCallback()) with a callback that makes assertions or throw exceptions, you have a Mock.

The steps required for creating a mock are:

  • create a mock by subclassing or coding an alternate implementation of an interface, as for all test doubles. Instance an object of your new class (PHPUnit does both steps for you out-of-the-box).
  • Define expectations in the arrange phase of the test.
  • Install the Test Double into the SUT.
  • Exercise the SUT by calling its methods.

Some verifications will be made immediately by the Mock(on parameters of a call), some other at the end of the test (like a method that had to be called 2 times but wasn't).

Note that PHPUnit clones parameter passed to Mocks in order to perform a strong verification at the end of the test. This will cause problems for example with isIdenticalTo() as the object used for verification is a clone and it's never identical (read: ===) to the one in the specification; use isInstanceOf() instead for a less strict expectation.

Variations

An hand-rolled Mock can be constructed by passing $this (the Testcase Object) to its constructor, or by simply throwing exceptions where the calls are incorrectly made (if the SUT allows exceptions to bubble up.)

Hand-rolled mocks may need additional step for verification, such as an assertion over the number of cals.

Generated mocks are one of the selling points of PHPUnit: the testing framework generates subclasses and instances an object for you; then you can configure the Mock expectations via a simple Api. In this case, the verification is always automatic, since PHUnit keeps a list of all instanced Mock Objects.

Examples

The code sample shows you two test which make use of Mocks. In the first, the Mock checks the number of calls it receives. In the second the Mock checks the passed parameters, and the number of calls which has to be 1.

Note that PHPUnit does not support different parameters expectations for different calls, but you can implement them with $this->returnCallback() and a custom closure.

<?php
class MockObjectTest extends PHPUnit_Framework_TestCase
{
/**
* In this test the UsersView Mock Object checks
* that it is called the right number of times
*/
public function testPrintsOnlyActiveUsers()
{
$users = array(
new User('george', true),
new User('john', false),
new User('mark', false),
new User('joan', true),
new User('steve', false)
);

$view = $this->getMock('UsersView');
$view->expects($this->exactly(2))
->method('add');

$sut = new UsersController($users);
$sut->renderOn($view);
}

public function testUsersSelectUsersWhoseNameStartsWithAGivenPrefix()
{
$users = array(
new User('george', true),
$john = new User('john', true),
new User('mark', true),
new User('steve', true)
);

$view = $this->getMock('UsersView');
$view->expects($this->once())
->method('add')
->with($john);

$sut = new UsersController($users, 'j');
$sut->renderOn($view);
}

}

/**
* The interface the Mock Objects implement. The simpler this interface,
* the cleaner your code.
*/
interface UsersView
{
public function add(User $user);
}

/**
* The System Under Test. It should render on a View, which is substituted by
* a Test Double.
*/
class UsersController
{
private $users;
private $prefixFilter;

public function __construct(array $users, $prefixFilter = '')
{
$this->users = $users;
$this->prefixFilter = $prefixFilter;
}

public function renderOn(UsersView $view)
{
foreach ($this->users as $user)
{
if ($user->isActive() && $user->startsWith($this->prefixFilter)) {
$view->add($user);
}
}
}
}

/**
* In these tests the instances of User will actually be Dummy, which
* means just objects which are passed around without any method call
* is performed on them.
* This implementation is thus really brief.
*/
class User
{
private $name;
private $active;

public function __construct($name, $active)
{
$this->name = $name;
$this->active = $active;
}

public function isActive()
{
return $this->active;
}

public function startsWith($prefix)
{
if ($prefix == '') {
return true;
}
if (strstr($this->name, $prefix) == $this->name) {
return true;
}
return false;
}
}
Mock object Object (computer science) PHP System under test Testing

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • Data Science Project Folder Structure
  • Exhaustive JUNIT5 Testing with Combinations, Permutations, and Products
  • What Developers Need to Know About Table Partition Pruning
  • 5 Benefits of Electronic Data Interchange

Comments

Web Dev Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • MVB Program
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends:

DZone.com is powered by 

AnswerHub logo