DZone
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
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones AWS Cloud
by AWS Developer Relations
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones
AWS Cloud
by AWS Developer Relations
  1. DZone
  2. Testing, Deployment, and Maintenance
  3. Testing, Tools, and Frameworks
  4. Practical PHP Testing Patterns: Configurable Test Double

Practical PHP Testing Patterns: Configurable Test Double

Giorgio Sironi user avatar by
Giorgio Sironi
·
Mar. 21, 11 · Interview
Like (0)
Save
Tweet
Share
913 Views

Join the DZone community and get the full member experience.

Join For Free

A Test Double is instantiated from a class, that can be reused by instancing different objects from it. This class is a child of an already existing class, or an alternate implementation of a known interface.

Thus some versions of the Test Double pattern (and of its specializations Stub, Mock, Spy and Fake) are called Configurable, since they share a common class whose objects are configured in each test.

A single Test Double object is fed the values to return, or the behavior to mimic, or the calls to expect and verify. This pattern is complemented to the Hard-Coded Test Double one, where a Test Double class is very specific and cannot be reused.

Implementation

The class source code for a Configurable Test Double can be generated or coded by hand, as this is orthogonal to the configurability/hard-coding choice.

In the case of coding by hand, you'll have to include some instance variables in the class.

Configuration can be performed via the constructor or additional methods not included in the original contract of the Test Double (and thus that are called only by the test code and not by the System Under Test, which does not know them.) This is called a separate Configuration Interface.

In this solution, you implement the methods you need, by hand. Some can be left empty if really not needed, but Configurable Test Doubles tend to be complete implementations where each method can return a value (in order for the reuse to span the whole test suite).

In the case of generation via PHPUnit, you get the Test Double as always via getMock() or getMockBuilder() and similar methods. You then configure it by calling expect(), which returns an expectation object where further details can be specified (like parameters, return values, method name, and so on).

Variations

Some additions can spice up this pattern.

  • Configuration Interface: a separate set of methods that can be used to configure the Test Double. The SUT does not know nor depend on them, and they are called in the test code only.
  • Configuration Mode: a record and playback interface where you call the methods on a Test Double in its initial state, or on an object linked to the Test Double, in exactly the same way you expect them to be called by the SUT.

While there is a fundamental choice of implementation on coding versus generation.

  • Hand-Built Test Double: Test Double coded by hand, and maintained as such.
  • Statically/Dynamically Generated Test Double: the other way around; Test Double whose code is generated by PHPUnit or another mocking framework. With PHPUnit it is always Dynamically Generated (at runtime instead of as a previous step), and I think it is true for all PHP implementations.

Example

In the code sample, we expand the Test Stub article's code and play with different variations of this pattern, and different Api to perform the configuration.

<?php
/**
* We expand the example of the Test Stub article in order to discuss
* configuration more than stubbing.
*/
class ConfigurableTestDoubleTest extends PHPUnit_Framework_TestCase
{
/**
* This Test Double is hand rolled and configurable: the constructor
* is the way chosen to perform configuration.
*/
public function testCalculatesAverageVisitorsNumber()
{
$source = new StubDataSource(array(40000, 50000, 100000, 20000, 40000));
$statistics = new Statistics($source);
$this->assertEquals(50000, $statistics->getAverage());
}

/**
* This Configurable Test Double use a separate interface (addSample())
* for the sake of configuration. This interface is oriented to ease of
* testing more than to the design of the production code.
*/
public function testCalculatesAverageVisitorsNumberFromAnotherTestDouble()
{
$source = new EasilyConfigurableStubDataSource;
$source->addSample(40000)->addSample(50000)->addSample(60000);
$statistics = new Statistics($source);
$this->assertEquals(50000, $statistics->getAverage());
}

/**
* You can easily generate Stub with PHPUnit. This time, the configuration
* is done via the expects() method, which return a configuration object.
*/
public function testCalculatesAverageVisitorsNumberWithGeneratedStub()
{
$source = $this->getMock('DataSource');
$source->expects($this->any())
->method('getSamples')
->will($this->returnValue((array(40000, 50000, 100000, 20000, 40000))));
$statistics = new Statistics($source);
$this->assertEquals(50000, $statistics->getAverage());
}

/**
* When you don't like PHPUnit's interface, or it is a too low level
* of abstraction, you can always wrap it and build something more friendly.
*/
public function testCalculatesAverageVisitorsNumberWithGeneratedStubAndAFriendlyInterface()
{
$source = $this->getMock('DataSource');
$this->addSamples($source, array(40000, 50000, 100000, 20000, 40000));

$statistics = new Statistics($source);
$this->assertEquals(50000, $statistics->getAverage());
}

private function addSamples($mock, array $samples)
{
$mock->expects($this->any())
->method('getSamples')
->will($this->returnValue($samples));
}
}

/**
* The System Under Test (same as previous article).
* It requires a DataSource collaborator to be used in production,
* or to be tested.
*/
class Statistics
{
private $source;

public function __construct(DataSource $source)
{
$this->source = $source;
}

public function getAverage()
{
$samples = $this->source->getSamples();
return array_sum($samples) / count($samples);
}
}

/**
* This is the contract of the source, the collaborator for the SUT.
* It's not mandatory to have an explicit interface, particularly in PHP,
* but it helps.
*/
interface DataSource
{
/**
* @return array numerical values of visitors to this website
*/
public function getSamples();
}

/**
* An Configurable Test Double configured via constructor.
*/
class StubDataSource implements DataSource
{
private $samples;

public function __construct(array $samples)
{
$this->samples = $samples;
}

public function getSamples()
{
return $this->samples;
}
}

/**
* Another Configurable Test Double, with a separate Configuration Interface.
*/
class EasilyConfigurableStubDataSource implements DataSource
{
private $samples = array();

/**
* @return EasilyConfigurableStubDataSource
*/
public function addSample($sample)
{
$this->samples[] = $sample;
return $this;
}

public function getSamples()
{
return $this->samples;
}
}
Test double Test stub PHP

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • The Beauty of Java Optional and Either
  • An End-to-End Guide to Vue.js Testing
  • Comparing Map.of() and New HashMap() in Java
  • Getting a Private SSL Certificate Free of Cost

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • 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: