PHPackages                             docteurklein/test-double-bundle - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Testing &amp; Quality](/categories/testing)
4. /
5. docteurklein/test-double-bundle

ActiveSymfony-bundle[Testing &amp; Quality](/categories/testing)

docteurklein/test-double-bundle
===============================

Easily create test doubles using symfony DIC

1.0.0(10y ago)4196.2k↓50%11[2 PRs](https://github.com/docteurklein/TestDoubleBundle/pulls)MITPHPPHP &gt;=5.4

Since Feb 27Pushed 8y ago1 watchersCompare

[ Source](https://github.com/docteurklein/TestDoubleBundle)[ Packagist](https://packagist.org/packages/docteurklein/test-double-bundle)[ RSS](/packages/docteurklein-test-double-bundle/feed)WikiDiscussions master Synced 1mo ago

READMEChangelog (1)Dependencies (3)Versions (3)Used By (0)

TestDoubleBundle
================

[](#testdoublebundle)

What ?
------

[](#what-)

A symfony bundle that eases creation of test doubles.

Using DIC tags, you can automatically replace a service with either a stub or a fake.

Why ?
-----

[](#why-)

To improve isolation of tests and increase the precision and variation of test fixtures.

Usually, our behat suite is using real data, coming from database fixtures.
This forces us to create gobal, universal, works-for-all fixtures.

A real database also implies to reset the state before each scenario.
This process is slow, and is just a workaround for having broken isolation.

An ideal test suite would run each scenario using only in-memory repositories.
Each scenario should define how the SUS behaves given a specific context.
Having a global implicit context (the database fixtures) makes it really hard to test different cases.

One solution is to replace your repositories with stubs.
Each scenario configures only the stubs required for it to work.

> Note: Stubbed data is not resilient across processes, and thus doesn't fit for end-to-end testing like a typical mink+behat suite.

But now that repositories are doubled, how do you know if your real repositories still work?
Well, that's the role of infrastructure tests. Only those run against a real backend, be it a database for repositories, or a server for an http client.

To access the real services, just use `.real`.

By doing that, you theoretically have a good coverage, isolation, speed
and you can better catch the origin of a regression.

All this while applying [modelling by example](http://stakeholderwhisperer.com/posts/2014/10/introducing-modelling-by-example).

How ?
-----

[](#how-)

### install

[](#install)

```
composer require docteurklein/test-double-bundle --dev

```

### register the bundle

[](#register-the-bundle)

```
    public function registerBundles()
    {
        $bundles = [
            new \DocteurKlein\TestDoubleBundle,
            // …
        ];

        return $bundles;
    }
```

> Note: You might want to add this bundle only in test env.

### integrate with behat

[](#integrate-with-behat)

This approach integrates very well with the [Symfony2Extension](https://github.com/Behat/Symfony2Extension/blob/master/doc/index.rst#injecting-services).

You can then inject the service and/or the prophecy in your context class.
You can also inject the container and access all the services at once.

Examples
--------

[](#examples)

> Note: The following examples use JmsDiExtraBundle to simplify code.

### Stubs

[](#stubs)

Stubs are created using [prophecy](https://github.com/phpspec/prophecy).

> Note: if you don't provide any tag attribute, then a stub is created. if no class or interface is given to the `stub` attribute, then a stub for the service class will be created. A stubbed class cannot be final.

- First, define a stub DIC tag for the service

```
/**
 * @Service("github_client")
 * @Tag("test_double", attributes={"stub"="GithubClient"})
 */
final class GuzzleClient implements GithubClient
{
    public function addIssue(Issue $issue)
    {
        // …
    }
}
```

- Automatically, the original `github_client` service is replaced with the `github_client.stub` service.

In order to control this stub, you have to use the `github_client.prophecy` service:

```
$issue = new Issue('test');
$container->get('github_client.prophecy')->addIssue($issue)->willReturn(true);
```

### Fake

[](#fake)

> Note: fakes are really just DIC aliases.

Imagine you have a service you want to double.

- First, create this service and add a tag with the corresponding fake service:

```
/**
 * @Service("github_client")
 * @Tag("test_double", attributes={"fake"="github_client.fake"})
 */
final class GuzzleClient implements GithubClient
{
    public function addIssue(Issue $issue)
    {
        // …
    }
}
```

- Then, create a fake implementation and register it with the fake id:

```
/**
 * @Service("github_client.fake")
 */
final class FakeClient implements GithubClient
{
    public function addIssue(Issue $issue)
    {
        // …
    }
}
```

### Behat

[](#behat)

> Note: We tagged `repo.invoices` and `http.client` as **stub**.

```
class Domain implements Context
{
    public function __construct($container)
    {
        $this->container = $container;
    }

    /**
     * @Given a building in "maintenance mode"
     */
    public function aBuildingInMaintenanceMode()
    {
        $this->building = new Building('BUILDING1337');
        $this->building->putInMaintenanceMode();
    }

    /**
     * @When its last unpaid invoice is being paid
     */
    public function itsLastUnpaidInvoiceIsBeingPaid()
    {
        $this->container
            ->get('repo.invoices.prophecy')
            ->findOneByReference('UNPAID04')
            ->willReturn(Invoice::ownedBy($this->building))
        ;
        $pay = $this->container->get('app.task.invoice.pay');
        $pay('UNPAID04');
    }

    /**
     * @Then it should be removed from maintenance mode
     */
    public function itShouldBeRemovedFromMaintenanceMode()
    {
        $this->container
            ->get('http.client.prophecy')
            ->removeFromMaintenanceMode('BUILDING1337')
            ->shouldHaveBeenCalled()
        ;

        $this->container->get('stub.prophet')->checkPredictions();
    }
}
```

###  Health Score

37

—

LowBetter than 83% of packages

Maintenance20

Infrequent updates — may be unmaintained

Popularity41

Moderate usage in the ecosystem

Community11

Small or concentrated contributor base

Maturity59

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

3734d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/f1c31c81fac8f125314208689f18ddecd5f31ffb10b5485641158f3d438b6263?d=identicon)[florian.klein@free.fr](/maintainers/florian.klein@free.fr)

---

Top Contributors

[![docteurklein](https://avatars.githubusercontent.com/u/109846?v=4)](https://github.com/docteurklein "docteurklein (1 commits)")

### Embed Badge

![Health badge](/badges/docteurklein-test-double-bundle/health.svg)

```
[![Health](https://phpackages.com/badges/docteurklein-test-double-bundle/health.svg)](https://phpackages.com/packages/docteurklein-test-double-bundle)
```

###  Alternatives

[behat/behat

Scenario-oriented BDD framework for PHP

4.0k96.8M2.0k](/packages/behat-behat)[drupal/drupal-driver

A collection of reusable Drupal drivers

6715.8M18](/packages/drupal-drupal-driver)[leanphp/behat-code-coverage

Generate Code Coverage reports for Behat tests

50359.8k2](/packages/leanphp-behat-code-coverage)[happyr/service-mocking

Make it easy to mock services in a built container

48253.8k2](/packages/happyr-service-mocking)[bab/tested-routes-checker-bundle

A bundle to ensure all routes of a Symfony application have been tested

122.9k](/packages/bab-tested-routes-checker-bundle)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
