Skip to content

feat: Behat extension#1085

Open
nikophil wants to merge 9 commits into2.xfrom
behat-extension
Open

feat: Behat extension#1085
nikophil wants to merge 9 commits into2.xfrom
behat-extension

Conversation

@nikophil
Copy link
Copy Markdown
Member

@nikophil nikophil commented Feb 6, 2026

This is a "meta PR" for the whole feature of the Behat extension + context

@aegypius @loic425 @mpdude the extension is now 90% ready! It would be super nice if you could give your thoughts about it 🙏

don't bother to review the whole massive PR, but the docs and the example features will help you to grasp what it does.

We've finally decided to ship an external package zenstruck/foundry-behat, mainly because Behat does not support SF 8.0 and it would have been a PITA to maintain the other way.

Currently Foundry won't ship any way to create objects in "natural language" nor a solution to use so-called "state methods" out of the box, I gave it a try and the complexity increased drastically, but this could be a nice improvement for the future.

Another future evolution would be to handle -ToMany relations which are currently not handled

fixes #1051
fixes #235

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a comprehensive Behat extension for Foundry, packaged as a separate composer package (zenstruck/foundry-behat). The extension provides seamless integration between Foundry's factory system and Behat's behavioral testing framework.

Changes:

  • Adds a complete Behat extension with step definitions for creating objects, making assertions, and managing fixtures
  • Implements database reset strategies (scenario, feature, manual, disabled) with DAMA DoctrineTestBundle support
  • Refactors kernel configuration signatures to use ContainerConfigurator for Symfony compatibility
  • Introduces fixture story resolution system with the FixtureStoryResolver class
  • Updates enum fixtures to use backed enums (IntBackedEnum, StringBackedEnum) for better type safety

Reviewed changes

Copilot reviewed 98 out of 101 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
src/Test/Behat/* Core Behat extension implementation including contexts, listeners, object registry, and factory resolution
src/Story.php Added event dispatching when state is added to stories for Behat integration
src/Command/LoadFixturesCommand.php Refactored to use FixtureStoryResolver instead of raw arrays
tests/Fixture/*Kernel.php Updated configureContainer signature to include ContainerConfigurator parameter
tests/Fixture/IntBackedEnum.php Renamed from SomeEnum and converted to int-backed enum
tests/Fixture/StringBackedEnum.php New string-backed enum for testing
tests/Fixture/Model/GenericModel.php Added additional fields for comprehensive testing (dateMutable, bool, float, enums)
config/persistence.php Updated service configuration for fixture story resolver
composer.json Excluded Behat extension from main package classmap
docs/index.rst Added extensive documentation for Behat integration (350+ lines)
.github/workflows/behat.yml New CI workflow for testing Behat extension
Comments suppressed due to low confidence (1)

tests/Fixture/IntBackedEnum.php:18

  • The enum has been renamed from SomeEnum (unit enum) to IntBackedEnum (int-backed enum) with values. This is a breaking change. Any existing code referencing SomeEnum will break. Verify that all references have been updated, especially in tests and documentation.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

*
* @template T of object
*/
final class StateAddedToStory
Copy link
Copy Markdown
Member Author

@nikophil nikophil Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I use this event to give a name to the objects created in story, so that we can access them the Behat features

@@ -0,0 +1,26 @@
#!/bin/bash
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this script is used to symlink ./src into ./src/Test/Behat/vendor/zenstruck/foundry

this improves DX a lot while working on the Behat extension

"symfony/polyfill-php84": "^1.33",
"symfony/polyfill-php85": "^1.33",
"symfony/string": "^6.4|^7.0",
"zenstruck/foundry": "dev-behat-extension"
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ 🚨 this needs to be changed at some point

Comment on lines +279 to +281
if (\class_exists(BehatServicesCompilerPass::class)) {
$container->addCompilerPass(new BehatServicesCompilerPass()); // @phpstan-ignore argument.type
}
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is the simpler way I found to plug the extension into our bundle. And it is the only place where the namespace Zenstruck\Foundry\Test\Behat is used in all Foundry's core repo

@nikophil nikophil force-pushed the behat-extension branch 3 times, most recently from a7d251e to 8c9cfd7 Compare February 6, 2026 16:05
nikophil and others added 3 commits February 7, 2026 12:27
* feature: Behat extension

* refactor(behat): don't use subtree split approach

* feat(behat): create FoundryContext

* feat(behat): create object with properties

* feat(behat): create multiple objects

* feat(behat): handle -ToOne relation ships

* feat(behat): transformer for last ids

* feat(behat): handle fixtures as stories

wip

* feat(behat): can name fixtures from story based on story state

* feat(behat): reset database by scenario

* feat(behat): reset database by feature

* feat(behat): introduce @resetDB tag

* feat(behat): provide support for Dama

* feat(behat): introduce @noResetDB tag

* feat(behat): short syntax for referencing objets

* feat(behat): handle correctly built in types

* feat(behat): handle correctly enums

* feat(behat): use main-dama as main testsuite

* feat(behat): tests scenario with errors

* refactor(behat): extract all normalization logic into FoundryCallFilter

* minor(behat): only load behat services when needed

* minor(behat): can load several fixtures on the same scenario

* minor(behat): rename exceptions without suffix

* tests(behat): test few behaviors with PHPUnit

* fix(behat): manual strategy should not reset the DB before the first test

* minor(behat): add behat file+line in exceptions

* fix(behat): make the CI green
* refactor: src/Test/Behat as a subpackage

* chore(behat): make tests phpunit pass

* chore(behat): make phpstan & behat tests working
nikophil and others added 5 commits February 7, 2026 21:41
…CallFilter

Extract the large array_map closure into 3 private methods:
normalizeTableRow(), resolveExplicitObjectReference(),
resolveObjectReferenceBasedOnPropertyType(). Use match expression
instead of if/continue chain. Add null coalescing throw for
array_shift and fix trailing whitespace.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…tNameResolver

Replace nested array_any/array_find_key calls with a classToShortName
cache built at boot() time, enabling O(1) lookups via isset() and
direct key access.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Make error messages more descriptive: include the number of rows
received and suggest using the "there are X with" step when trying
to create multiple objects.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move the (?!\d) negative lookahead inside the branch reset group,
just before the unquoted \S+ alternative. This allows quoted names
like "007" to start with a digit while still preventing ambiguity
with count patterns for unquoted names.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add AbstractUid support via toRfc4122() conversion for entities
using UUID/ULID identifiers. Add unit test and Behat functional
scenario.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@nikophil nikophil force-pushed the behat-extension branch 2 times, most recently from 1ca710a to d93d3cb Compare February 8, 2026 10:42
Add a note explaining that the <ref(type, name)> syntax is a fallback
mechanism for edge cases, and that automatic resolution should be
preferred.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Comment on lines +42 to +45
#[Given('there is a(n) :factoryShortName with')]
#[Given('there is a(n) :factoryShortName :objectName with')]
#[Given('there is a(n) :factoryShortName called :objectName with')]
#[Given('there is a(n) :factoryShortName named :objectName with')]
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#[Given('there is a(n) :factoryShortName with')]
#[Given('there is a(n) :factoryShortName :objectName with')]
#[Given('there is a(n) :factoryShortName called :objectName with')]
#[Given('there is a(n) :factoryShortName named :objectName with')]
#[Given('there is a(n) :factoryShortName with:')]
#[Given('there is a(n) :factoryShortName :objectName with:')]
#[Given('there is a(n) :factoryShortName called :objectName with:')]
#[Given('there is a(n) :factoryShortName named :objectName with:')]

I think that with behat, it's common to finish a line with : when it's followed by a TableNode.

Examples in behat codebase or in soyuka context or even in the cucumber gherkin reference guide.

class FoundryContext extends AbstractFoundryContext implements Context
{
#[\Override]
#[Given('there is a(n) :factoryShortName')]
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't those strings be too generic ? It may override, or be overridden by other contexts ?
Shouldn't it contain a reference to foundry (or at least "fixture") ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

Behat extension ideas Behat Integration

3 participants