Skip to content

Commit

Permalink
feat: allow service story in WithStory attribute
Browse files Browse the repository at this point in the history
  • Loading branch information
nikophil committed Dec 4, 2024
1 parent c972482 commit 61b07ae
Show file tree
Hide file tree
Showing 8 changed files with 139 additions and 54 deletions.
109 changes: 74 additions & 35 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1608,35 +1608,18 @@ PHPUnit Data Providers

It is possible to use factories in
`PHPUnit data providers <https://phpunit.readthedocs.io/en/9.3/writing-tests-for-phpunit.html#data-providers>`_.
Their usage depends on which Foundry version you are running:
Their usage depends on whether you're using Foundry's `PHPUnit Extension`_ or not.:

Data Providers with Foundry ^2.2
................................

From version 2.2, Foundry provides an extension for PHPUnit.
You can install it by modifying you ``phpunit.xml.dist``:

.. configuration-block::

.. code-block:: xml
<phpunit>
<extensions>
<bootstrap class="Zenstruck\Foundry\PHPUnit\FoundryExtension"/>
</extensions>
</phpunit>
.. warning::

This PHPUnit extension requires at least PHPUnit 11.4.
With PHPUnit Extension
......................

Using this extension will allow to use your factories in your data providers the same way you're using them in tests.
Thanks to it, you can:
Thanks to Foundry's `PHPUnit Extension`_, you'll be able to use your factories in your data providers the same way
you're using them in tests. Thanks to it, you can:
* Call ``->create()`` or ``::createOne()`` or any other method which creates objects in unit tests
(using ``PHPUnit\Framework\TestCase``) and functional tests (``Symfony\Bundle\FrameworkBundle\Test\KernelTestCase``)
* Use `Factories as Services`_ in functional tests
* Use `faker()` normally, without wrapping its call in a callable

::

use App\Factory\PostFactory;
Expand All @@ -1654,21 +1637,30 @@ Thanks to it, you can:
yield [PostWithServiceFactory::createOne()];
yield [PostFactory::createOne(['body' => faker()->sentence()];
}

.. versionadded:: 2.2

The ability to call ``Factory::create()`` in data providers was introduced in Foundry 2.2.

.. warning::

You will need at least PHPUnit 11.4 to call ``Factory::create()`` in your data providers.

.. warning::

Because Foundry is relying on its `Proxy mechanism <object-proxy>`_, when using persistence,
your factories must extend ``Zenstruck\Foundry\Persistence\PersistentProxyObjectFactory`` to work in your data providers.

.. warning::

For the same reason, you should not call methods from `Proxy` class in your data providers,
not even ``->_real()``.
For the same reason, you should not call methods from `Proxy` class in your data providers, not even ``->_real()``.


Without PHPUnit Extension
.........................

Data Providers before Foundry v2.2
..................................
Data providers are computed early in the phpunit process before Foundry is booted.
Be sure your data provider returns only instances of ``Factory`` and you do not try to call ``->create()`` on them:

::

Expand All @@ -1690,11 +1682,6 @@ Data Providers before Foundry v2.2
yield [PostFactory::new()->published()];
}

.. note::

Be sure your data provider returns only instances of ``Factory`` and you do not try to call ``->create()`` on them.
Data providers are computed early in the phpunit process before Foundry is booted.

.. note::

For the same reason as above, it is not possible to use `Factories as Services`_ with required
Expand Down Expand Up @@ -2168,6 +2155,35 @@ Objects can be fetched from pools in your tests, fixtures or other stories:
ProvinceStory::getRandomRange('be', 1, 4); // between 1 and 4 random Province|Proxy's from "be" pool
ProvinceStory::getPool('be'); // all Province|Proxy's from "be" pool

#[WithStory] Attribute
~~~~~~~~~~~~~~~~~~~~~~

You can use the ``#[WithStory]`` attribute to load stories in your tests:

::

Check failure on line 2163 in docs/index.rst

View workflow job for this annotation

GitHub Actions / Lint Documentation

Please reorder the use statements alphabetically

use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Zenstruck\Foundry\Attribute\WithStory;
use App\Story\CategoryStory;

// You can use the attribute on the class...
#[WithStory(CategoryStory::class)]
final class NeedsCategoriesTest extends KernelTestCase
{
// ... or on the method
#[WithStory(CategoryStory::class)]
public function testThatNeedStories(): void
{
// ...
}
}

If used on the class, the story will be loaded before each test method.

.. warning::

The `PHPUnit Extension`_ for Foundry is needed to use ``#[WithStory]`` attribute.

Static Analysis
---------------

Expand All @@ -2181,6 +2197,29 @@ Please, enable it with:
$ vendor/bin/psalm-plugin enable zenstruck/foundry
PHPUnit Extension
-----------------

Foundry is shipped with an extension for PHPUnit. You can install it by modifying the file ``phpunit.xml.dist``:

.. configuration-block::

.. code-block:: xml
<phpunit>
<extensions>
<bootstrap class="Zenstruck\Foundry\PHPUnit\FoundryExtension"/>
</extensions>
</phpunit>
This extension provides the following features:
- support for the `#[WithStory] Attribute`_
- ability to use ``Factory::create()`` in `PHPUnit Data Providers`_ (along with PHPUnit ^11.4)

.. note::

The extension was introduced in Foundry 2.2, and is only compatible with PHPUnit ^10.

Bundle Configuration
--------------------

Expand Down
4 changes: 2 additions & 2 deletions phpunit
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ fi
DAMA_EXTENSION="DAMA\DoctrineTestBundle\PHPUnit\PHPUnitExtension"
FOUNDRY_EXTENSION="Zenstruck\Foundry\PHPUnit\FoundryExtension"

if [ "${USE_FOUNDRY_PHPUNIT_EXTENSION:-0}" = "1" ] && [ "${PHPUNIT_VERSION}" != "11" ]; then
echo "❌ USE_FOUNDRY_PHPUNIT_EXTENSION could only be used with PHPUNIT_VERSION=11";
if [ "${USE_FOUNDRY_PHPUNIT_EXTENSION:-0}" = "1" ] && [ "${PHPUNIT_VERSION}" = "9" ]; then
echo "❌ USE_FOUNDRY_PHPUNIT_EXTENSION cannot be used with PHPUNIT_VERSION=10";
exit 1;
fi

Expand Down
20 changes: 9 additions & 11 deletions src/PHPUnit/FoundryExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,26 +24,24 @@
*/
final class FoundryExtension implements Runner\Extension\Extension
{
public const MIN_PHPUNIT_VERSION = '11.4';

public function bootstrap(
TextUI\Configuration\Configuration $configuration,
Runner\Extension\Facade $facade,
Runner\Extension\ParameterCollection $parameters,
): void {
if (!ConstraintRequirement::from(self::MIN_PHPUNIT_VERSION)->isSatisfiedBy(Runner\Version::id())) {
throw new \LogicException(\sprintf('Your PHPUnit version (%s) is not compatible with the minimum version (%s) needed to use this extension.', Runner\Version::id(), self::MIN_PHPUNIT_VERSION));
}

// shutdown Foundry if for some reason it has been booted before
if (Configuration::isBooted()) {
Configuration::shutdown();
}

$facade->registerSubscribers(
new BootFoundryOnDataProviderMethodCalled(),
new ShutdownFoundryOnDataProviderMethodFinished(),
new BuildStoryOnTestPrepared(),
);
$subscribers = [new BuildStoryOnTestPrepared()];

if (ConstraintRequirement::from('11.4')->isSatisfiedBy(Runner\Version::id())) {
// those deal with data provider events which can be useful only if PHPUnit 11.4 is used
$subscribers[] = new BootFoundryOnDataProviderMethodCalled();
$subscribers[] = new ShutdownFoundryOnDataProviderMethodFinished();
}

$facade->registerSubscribers(...$subscribers);
}
}
36 changes: 36 additions & 0 deletions tests/Fixture/Stories/ServiceStory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

/*
* This file is part of the zenstruck/foundry package.
*
* (c) Kevin Bond <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Zenstruck\Foundry\Tests\Fixture\Stories;

use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\Routing\RouterInterface;
use Zenstruck\Foundry\Story;
use Zenstruck\Foundry\Tests\Fixture\Factories\Entity\GenericEntityFactory;

/**
* @author Nicolas PHILIPPE <[email protected]>
*/
final class ServiceStory extends Story
{
public function __construct(
private readonly RouterInterface $router
) {
}

public function build(): void
{
$this->addState(
'foo',
GenericEntityFactory::createOne(['prop1' => $this->router->getContext()->getHost()])
);
}
}
2 changes: 2 additions & 0 deletions tests/Fixture/TestKernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
use Zenstruck\Foundry\Tests\Fixture\Factories\Object1Factory;
use Zenstruck\Foundry\Tests\Fixture\Stories\GlobalInvokableService;
use Zenstruck\Foundry\Tests\Fixture\Stories\GlobalStory;
use Zenstruck\Foundry\Tests\Fixture\Stories\ServiceStory;
use Zenstruck\Foundry\ZenstruckFoundryBundle;

/**
Expand Down Expand Up @@ -162,6 +163,7 @@ protected function configureContainer(ContainerBuilder $c, LoaderInterface $load
$c->register(GlobalInvokableService::class);
$c->register(ArrayFactory::class)->setAutowired(true)->setAutoconfigured(true);
$c->register(Object1Factory::class)->setAutowired(true)->setAutoconfigured(true);
$c->register(ServiceStory::class)->setAutowired(true)->setAutoconfigured(true);
}

protected function configureRoutes(RoutingConfigurator $routes): void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@

/**
* @author Nicolas PHILIPPE <[email protected]>
* @requires PHPUnit 11.4
* @requires PHPUnit 10
*/
#[RequiresPhpunit('11.4')]
#[RequiresPhpunit('10')]
#[RequiresPhpunitExtension(FoundryExtension::class)]
#[WithStory(EntityStory::class)]
final class WithStoryOnClassTest extends KernelTestCase
Expand Down
14 changes: 12 additions & 2 deletions tests/Integration/Attribute/WithStory/WithStoryOnMethodTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,14 @@
use Zenstruck\Foundry\Tests\Fixture\Factories\Entity\GenericEntityFactory;
use Zenstruck\Foundry\Tests\Fixture\Stories\EntityPoolStory;
use Zenstruck\Foundry\Tests\Fixture\Stories\EntityStory;
use Zenstruck\Foundry\Tests\Fixture\Stories\ServiceStory;
use Zenstruck\Foundry\Tests\Integration\RequiresORM;

/**
* @author Nicolas PHILIPPE <[email protected]>
* @requires PHPUnit 11.4
* @requires PHPUnit 10
*/
#[RequiresPhpunit('11.4')]
#[RequiresPhpunit('10')]
#[RequiresPhpunitExtension(FoundryExtension::class)]
final class WithStoryOnMethodTest extends KernelTestCase
{
Expand All @@ -47,4 +48,13 @@ public function can_use_multiple_story_in_attribute(): void
{
GenericEntityFactory::assert()->count(5);
}

/**
* @test
*/
#[WithStory(ServiceStory::class)]
public function can_use_service_story(): void
{
$this->assertSame('localhost', ServiceStory::get('foo')->getProp1());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@

/**
* @author Nicolas PHILIPPE <[email protected]>
* @requires PHPUnit 11.4
* @requires PHPUnit 10
*/
#[RequiresPhpunit('11.4')]
#[RequiresPhpunit('10')]
#[RequiresPhpunitExtension(FoundryExtension::class)]
#[WithStory(EntityPoolStory::class)]
final class WithStoryOnParentClassTest extends ParentClassWithStoryAttributeTestCase
Expand Down

0 comments on commit 61b07ae

Please sign in to comment.