-
-
Notifications
You must be signed in to change notification settings - Fork 108
Description
Here are the features I think a Behat extension for Foundry should have.
@aegypius @mpdude @mellowgrab @kevin-schmitt @roboticflamingo any thoughts about this?
Any help about the API and the naming of the Given, When, Then definitions would be appreciated
1. Create objects with PyTable
Create one object
Given a post A is created with properties
| name | state | published_at |
| my awesome post | published | <now()> |Warning
Maybe a Gherkin definition like a post A is created with properties is too generic and would create conflicts with contexts from userland?
Note
We need to find a way to resolve the object name to a factory class:
"post" => PostFactory, "post category" => PostCategoryFactory
Solutions could be:
- Automagically resolve the factory name from the class's shortname, based on camelCase. If a conflict is detected, we should throw an exception or fallback on another solution
- introduce an attribute
#[FactoryName('post')] - possibility to configure this in
behat.yml:extensions: Zenstruck\Foundry\Test\Behat\FoundryExtension: factories: App\Factory\PostFactory: post App\Factory\PostCategoryFactory: post category
Create multiple objects
Given posts are created with properties
| _ref | name | state | published_at |
| A | my awesome post A | published | <now()> |
| B | my awesome post B | draft | <now()> |Note
We need a way to handle pluralized names:
- use an inflector
- Handle it in the attribute:
#[FactoryName(shortName: 'post', plural: 'posts')] - Or in
behat.ymlextensions: Zenstruck\Foundry\Test\Behat\FoundryExtension: factories: App\Factory\PostFactory: {shortName: post, plural: posts}
Can reference another object when creating an object
Given a post category A is created with properties
| name |
| my awesome category |
Given a post A is created with properties
| name | category |
| my awesome post | A |2. Create objects with natural language
This is the more challenging feature
Create an object with zero or one property
Given a post A is createdGiven a post A is created with name "awesome post"Warning
This definition a post A is created is even more generic than the previous ones.
I think that we should find something more specific.
Maybe a fixture post named A is created? a post A is created by Foundry?
Create an object and reuse it in a later step
Given a post A is created
Given the post A has property "name" "awesome post"
Given this post has property "state" "published"Note
This would mean that the objects are not created right away at the first line
So we need to store the factories somewhere, and create the objects before the first When
Reference an object in a later step
Given a post category A is created
And a post A is created
And the post A has category "post category B"Note
We should detect that Post::$category is an object, and not a string
and then, we need to use a factory
Warning
In order not to create multiple categories in this example, we will need to leverage lazy() or memoize() helpers
Be able to call factory state methods
Given this factory:
class PostFactory extenbds PersistentObjectFactory
{
public function publishedAWeekAgo(): self
{
return $this->with([
'published' => true,
'published_at' => now('-1 week'),
]);
}
}We should be able to reference to the "state method" in a later step:
Given a post A is created
Given the post A was published a week agoWarning
I don't like this syntax, we must find something better
Note
We'd also need a way to pass one or more arguments to the state method
Note
The state method could be resolved automagically,
but we should also add a way to configure it, with an attribute #[FactoryStateMethod('published a week ago')]
3. Create objects within a Story
Given this story:
class PostStory extends Story
{
public function build(): void
{
$this->addState('my blog post', PostFactory::createOne());
}
}@withStory postStory
When I do something...Note
Once again, we need to resolve the story from this short name
This could be done automagically, from the class's short name
or we could leverage #[AsFixture()] attribute
4. Provide a way to access the created object in When and Then steps
Foundry could expose some sort of "objects registry", where we can store the created objects.
Given a post A is createduse Zenstruck\Foundry\Test\Behat\ObjectsRegistry;
class FoundryContext
{
public function __construct(
private ObjectsRegistry $objectsRegistry
) {}
#[Given('a :object is created')]
public function createObject(string $objectName): void
{
$object = // somehow create the object with Foundry
$this->objectsRegistry->store($objectName, $object);
}
}This object registry could be injected in userland contexts, and be used to easily "act" and "assert" on the created objects.
Note
Objects created within a story should also be accessible from the registry
Note
Of course, the object registry should handle name conflicts
Note
The object registry should be reset between scenarios (or features? or on demand?)
5. Provide a way to make simple assertions on the created objects
Given a post A is created with properties
| name | state | published_at |
| my awesome post | published | <now()> |
# this would use a userland context
When I unpublish the post A
# From Foundry extension
Then the post A property "state" is "unpublished"
Then the post A properties are now
| state | unpublished_at |
| unpublished | <now()> |6. Reset the database
We should provide a way to reset the database between scenarios (or between features? or on demand?) the same way as
ResetDatabase trait does in PHPUnit.
The database reset could be configured to run automatically, this would be configured by the extension configuration:
extensions:
Zenstruck\Foundry\Test\Behat\FoundryExtension:
reset_database: before_scenario # or before_feature(or maybe this should be done in the bundle's configuration?)
And for maximum flexibility, it could also be triggered manually, with a tag
Feature something
@resetDB
Scenario do something with fresh DB
Given / When / Then
Scenario another scenario using the same DB
Given / When / Then
@resetDB
Scenario do something with fresh DB
Given / When / ThenThe reset database mechanism should work with and without dmaicher/doctrine-test-bundle
Warning
This will be problematic if we want the database to be reset at another pace than before every scenario, because dama
rollbacks the transaction after each scenario
and this is not configurable.