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
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

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
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

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Related

  • Using AUTHID Parameter in Oracle PL/SQL
  • Organizing Knowledge With Knowledge Graphs: Industry Trends
  • Best Practices for Building the Data Pipelines
  • Discrepancies Between Test and FastAPI App Data

Trending

  • Performing and Managing Incremental Backups Using pg_basebackup in PostgreSQL 17
  • Automating Data Pipelines: Generating PySpark and SQL Jobs With LLMs in Cloudera
  • Ethical AI in Agile
  • Agentic AI for Automated Application Security and Vulnerability Management
  1. DZone
  2. Data Engineering
  3. Databases
  4. Practical PHP Testing Patterns: Fake Object

Practical PHP Testing Patterns: Fake Object

By 
Giorgio Sironi user avatar
Giorgio Sironi
·
Mar. 16, 11 · Interview
Likes (0)
Comment
Save
Tweet
Share
3.8K Views

Join the DZone community and get the full member experience.

Join For Free

The purpose of a Fake Object, a kind of Test Double, is to replace a collaborator with a functional copy. While Mocks prefer a specification of the behavior to check, Fake Objects are really a simplified version of the production object they substitute.

A good Fake Object, however, will be very lightweight, easy to create and throw away to ensure test isolation. Usually it won't satisfy some other functional or non-functional requirements, otherwise we would use him instead of the real object. For example a UserRepository, which normally stores users in a database, may be substitued by a Fake that:

  • does not send mails when an user is added.
  • Doesn't really save anything in the database, but keeps a list in an array.
  • When some complex method is called, just throws a NonImplementedException.

Utility

Of course performance and less brittleness of tests are the main selling points of a Fake Objects: think about testing with a real database and without one; however this is true for all Test Doubles and I don't need to repeat it.
Often the Fake is used when the contract between the SUT and the collaborator is too complex to effectively build a Stub or Mock:

  • many methods, or many calls to the same method are made.
  • Return parameters are difficult to setup.

A Fake avoids overspecifying a test for a very invasive contract, like the order of calls and precise parameters; a real implementation is more robust to changes in the contract.

For example if the Fake is a collection, you can store and retrieve objects of another type, and still not change the Fake; expectations and configured return value instead would change (together).

As an example, an embryonal Fake is PHPUnit $this->returnArgument() for the will() Mock expectation. It's a piece of functionality which substitutes an hard-coded expectation, in order to return one of the arguments of the call instead of adding the argument to the expectation itself.

Note that many times we don't have to create new code to leverage Fakes: we can use the existing one with a different configuration (like a Cache maximumCachedItems=0, or with an adapter that use an in-memory cache instead of APC or Memcache) or something provided for us, like an sqlite in-memory database created by PDO. NullObjects are sometimes Fake, but are used in production more than in testing.

Implementation

Several steps are necessary for building an hand-rolled Fake:
  • create the Fake class by hand, and define the simplest implementation that could do the job for this test. Remember, you're not testing the Fake, you're testing the SUT, so the Fake doesn't have to be perfect, but just passable for the test case at end.
  • Instance an object from the Fake class: the constructor may be different from the real collaborator's one as it's not part of the contract.
  • Install the Fake into the SUT, and proceed as normal.
The steps for generating a Fake with PHPUnit are a little different; however, you will still have to write its businss logic:
  • create the "mock" as always via getMock() or getMockBuilder().
  • Define expectations for its methods with will($this->returnCallback()) and some anonymous functions (or via self-shunting like we did for "Stubs"). Often objects passed in this closures (such as ArrayObject instances) can aid in making the Fake methods communicate with each other.
  • Install the Fake and proceed with the test.
The second approach is invaluable when you have a single method on the Fake: it's very fast.

Variations

  • Fake Database: an alternative implementation of the Database Facade that lets you test classes that depend on the database without actually using it. For exaple, a DAO which internally use an SplObjectStorage instead of the connection.
  • In-Memory Database: sqlite3 in-memory database used with ORMs for tests that isolate you from the real database, but not from using PDO. High Return On Investment.
  • Fake Web Service: mimics the interface of some web service like Google Analytics, when you have to interact also in write and not only in read.
  • Fake Service Layer: simplified implementations of Service Layer classes, which avoid checking concurrency, authorization, authentication and so on in order to simplify functional testing.

Example

In this code sample, the test target a ForumManager class which needs to manipulate Posts. It's not a simple Facade: it moves Post around, merges threads and so on. So we inject in it a FakePostDao: ForumManager will call it instead of the database.

When you I have many tests of this type, the time for writing the Fake implementation is well spent.

<?php
/**
* The System Under Test should use this database-related class to move Posts around.
* However we inject a FakePostDao in order to avoid using a real database in this test,
* and at the same time better define the interface between the two.
*/
class FakeObjectTest extends PHPUnit_Framework_TestCase
{
public function testMergesTwoThreads()
{
$dao = new FakePostDao(array(
1 => array(
new Post('Hello'),
new Post('Hello!'),
new Post('')
),
2 => array(
new Post('Hi'),
new Post('Hi!')
),
3 => array(
new Post('Good morning.')
)
));

$forumManager = new ForumManager($dao);
$forumManager->mergeThreadsByIds(1, 2);
$thread = $dao->getThread(1);
$this->assertEquals(5, count($thread));
}
}

/**
* The SUT.
*/
class ForumManager
{
private $dao;

public function __construct(PostsDao $dao)
{
$this->dao = $dao;
}

public function mergeThreadsByIds($originalId, $toBeMergedId)
{
$original = $this->dao->getThread($originalId);
$toBeMerged = $this->dao->getThread($toBeMergedId);
$newOne = array_merge($original, $toBeMerged);
$this->dao->removeThread($originalId);
$this->dao->removeThread($toBeMergedId);
$this->dao->addThread($originalId, $newOne);
}
}

/**
* Interface for the collaborator to substitute with the Test Double.
*/
interface PostsDao
{
public function getThread($id);
public function removeThread($id);
public function addThread($id, array $thread);
}

/**
* Fake implementation.
*/
class FakePostDao implements PostsDao
{
private $threads;

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

public function getThread($id)
{
return $this->threads[$id];
}

public function removeThread($id)
{
unset($this->threads[$id]);
}

/**
* We model Thread as array of Posts for simplicity.
*/
public function addThread($id, array $thread)
{
$this->threads[$id] = $thread;
}
}

/**
* Again a Dummy object: minimal implementation, to make this test pass.
*/
class Post
{
}
Object (computer science) Database PHP Testing In-memory database

Opinions expressed by DZone contributors are their own.

Related

  • Using AUTHID Parameter in Oracle PL/SQL
  • Organizing Knowledge With Knowledge Graphs: Industry Trends
  • Best Practices for Building the Data Pipelines
  • Discrepancies Between Test and FastAPI App Data

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

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

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!