silverstripe-behat-extension icon indicating copy to clipboard operation
silverstripe-behat-extension copied to clipboard

FixtureFactory does not apply data relations correctly

Open raissanorth opened this issue 6 years ago • 7 comments

I am trying to add an ElementalPageExtension to Page via the ConfigContext.

It appears that the FixtureFactory is not saving the relations correctly. At the time of object creation the extension is not applied. If we add the extension manually before creating the object it almost works, but we have to query the database directly, so that the relation gets saved correctly in the database.

I have in my feature file: Given I have a config file "enable-elemental.yml". The yml file has this content:

---
Name: testonly-enable-elemental
---
Page:
  extensions:
    - DNADesign\Elemental\Extensions\ElementalPageExtension

I have added some fixture context like:

/**
* Create a page containing an ElementalArea and an Element
* Example: Given a "Blocks Page" "page" has a "Block Title" content element with "Sample content" content
*
* @Given the :pageTitle :type has a :elementTitle content element with :elementContent content
*
* @param string $pageTitle
* @param string $type
* @param string $elementTitle
* @param string $elementContent
*/
public function theHasAContentElementWithContent($pageTitle, $type, $elementTitle, $elementContent)
{
    $targetClass = $this->convertTypeToClass($type);
    $targetObj = $this->getFixtureFactory()->createObject($targetClass, $pageTitle);
    $area = $targetObj->ElementalArea()
    // Throws exception "method ElementalArea does not exist"! But the extension should add it?

    // Add blocks to elemental area for testing...
}

After adding $targetClass::add_extension(ElementalPageExtension::class); before the fixture factory call, the fixture context works and we can add elements but when the elemental area loads on the page during the behat test it has got a new elemental area?!

We can resolve this by directly querying the database to actually map the relation correctly eg:

DB::query("INSERT INTO Page (ID, ElementalAreaID) VALUES ($targetObj->ID, $targetObj->ElementalAreaID)");

However this does not feel like it's working correctly at all.

raissanorth avatar Aug 08 '18 05:08 raissanorth

Can you add another Given I visit "dev/build?flush=1" (or similar step) after setting the config, but prior to setting up the fixture rows? maybe that will fix it.

tractorcow avatar Aug 08 '18 22:08 tractorcow

Hey @tractorcow. Raissa had that in the feature. Forgot to mention that!

It's also interesting that without any hacks like ::addExtension the extension just does not exist in the fixture context part, but the pages have elemental areas during the rest of the Behat test.

ScopeyNZ avatar Aug 08 '18 23:08 ScopeyNZ

Oh right, it won't work because you need to add the extension to the context of the CLI process running behat, not the web process getting flushed.

In that case, you may need yet another behat fixture for and I add an extension "" to "" class, which occurs in the CLI context. sorry!

tractorcow avatar Aug 08 '18 23:08 tractorcow

That feels like something we could PR into this repo? We'll test if it works first.

ScopeyNZ avatar Aug 08 '18 23:08 ScopeyNZ

New fixture step for and I add an extension... works

robbieaverill avatar Aug 13 '18 23:08 robbieaverill

There's a bit of scaffolding required for this:

  • Create a config file
  • Use the step "I have config file..."
  • Step "visit dev/build?flush"
  • Custom step "and I add an extension"

I think we should add a step to this repo that does all that automatically, then we don't have the weird double up of a config file and the custom step so the CLI context knows what's going on. Thoughts?

Additionally - is there a way we can avoid the dev/build if we know specifically we are adding this one extension? Adds a lot of time to each scenario.

ScopeyNZ avatar Aug 14 '18 00:08 ScopeyNZ

We're going to change the background to be a list of UI steps to create elements rather than use a fixture factory.

We've tried to use the fixture factory to create a Page -> has_one -> ElementalArea (created onBeforeWrite in Page) -> has_many -> ElementContent assigned to the ElementalArea. When placing a breakpoint after this fixture definition and inspecting the database we see the relationship is all set up correctly, however when the Behat test runs the page edit form the following logic executes:

# File: ElementalAreasExtension::updateCMSFields
// Example: $eaRelationship = 'ElementalArea';
$area = $this->owner->$eaRelationship();

// if area isn't in the database then force a write so the blocks have a parent ID.
if (!$area->isInDb()) {
    $area->write();

    $this->owner->{$key} = $area->ID;
    $this->owner->write();
}

This creates a new ElementalArea with ID 7 (instead of ID 6 from fixture factory as we previously checked) and assigns that to the page instead, meaning when the list of elements loads it has nothing in it because the fixtured ElementContent belongs to ElementalArea with ID 6, not 7.

It seems like there's a disconnect between states in the fixture factory and the actual UI during the Behat test. Perhaps cache/config etc... we give up and use UI step definitions in the background instead of fixture context definitions.

For reference, here's the definition we were trying:

/**
 * Create a page cothe "Blocks Page" :type has a "Block Title" content element with "Sample content" content
 *
 * @Given the :pageTitle :type has a :elementTitle content element with :elementContent content
 *
 * @param string $pageTitle
 * @param string $type
 * @param string $elementTitle
 * @param string $elementContent
 */
public function theHasAContentElementWithContent($pageTitle, $type, $elementTitle, $elementContent)
{
    // Create the page (ElementalArea is created on write and attached to it)
    $targetClass = $this->convertTypeToClass($type);

    $page = $this->getFixtureFactory()->get($targetClass, $pageTitle);
    if (!$page) {
        $page = $this->getFixtureFactory()->createObject($targetClass, $pageTitle);
    }
    // Ensure that an ElementalArea is created (see ElementalAreasExtension)
    $page->write();

    // Create the element and assign it to the page's ElementalArea
    $element = $this->getFixtureFactory()->createObject(ElementContent::class, $elementTitle);
    $element->ParentID = $page->ElementalAreaID;
    $element->Content = $elementContent;
    $element->write();

    $page->ElementalArea()->write();
}

Example step:

And the "Blocks Page" "page" has a "Block Title" content element with "Sample content" content

raissanorth avatar Aug 14 '18 04:08 raissanorth