9

In a functional test of a form to add members of an ArrayCollection there is this statement:

$form['client[members][1][fname]'] = 'Benny';

The field name was verified with a DOM inspector.

The console output at this line is:

InvalidArgumentException: Unreachable field "members"

G:\Documents\workspace\sym\vendor\symfony\symfony\src\Symfony\Component\DomCrawler\Form.php:459
G:\Documents\workspace\sym\vendor\symfony\symfony\src\Symfony\Component\DomCrawler\Form.php:496
G:\Documents\workspace\sym\vendor\symfony\symfony\src\Symfony\Component\DomCrawler\Form.php:319
G:\Documents\workspace\sym\src\Mana\ClientBundle\Tests\Controller\ClientControllerTest.php:57

What method should be used to test the addition of an ArrayCollection member?

Edit as requested (n.b., follow redirects is on):

    //link to trigger adding household member form
    $link = $crawler->selectLink('Add household member')->link();
    $crawler = $client->click($link);
    $form = $crawler->selectButton('Add client')->form();
    $form['client[members][1][fname]'] = 'Benny';
    $form['client[members][1][dob]'] = "3/12/1999";
    $crawler = $client->submit($form);
    $this->assertTrue($crawler->filter('html:contains("Client View Form")')->count() > 0);
geoB
  • 4,578
  • 5
  • 37
  • 70
  • Can you [var_dump](http://php.net/manual/en/function.var-dump.php) `$form` please? – Thomas Potaire Mar 16 '13 at 21:36
  • Need to figure out how to get complete $form in a format that will be useful. Windows console abbreviates such that there is no useful information. var_dump to output buffer does not add anything. – geoB Mar 16 '13 at 21:54
  • Oh right because the output is too large, nevermind. Could you copy/paste your asserts and the code that leads to it? – Thomas Potaire Mar 16 '13 at 21:59
  • Included in edit above. Did learn that one can capture var_export($form) into the buffer then put in a file - there's the large output. – geoB Mar 16 '13 at 22:11
  • Could you gist the content of your html page ? – AdrienBrault Mar 17 '13 at 11:18
  • @AdrienBrault: Hope this is what you're looking for: [link](https://gist.github.com/truckee/5181505) – geoB Mar 17 '13 at 13:36
  • Have you tried doing something like `$form['client[members][1][fname]']->setValue('Benny');` ? – Adam Elsodaney Mar 17 '13 at 14:30
  • @Adam-E:Not tried that. I have, however, done a `var_export($form, true);` and dumped that to a file. A search of the resulting file shows no instance of `client[members]`. My conclusion is that the DOM crawler is not picking up the form generated by javascript. I can see the variable when I run the code and use a DOM inspector so I know it exists. Is there some method that assures the crawler will pick up on this? – geoB Mar 17 '13 at 17:02
  • @geoB I think you're right in that if part of the form is generated by JavaScript and not Symfony, the DomCrawler will not know it's there. Perhaps something like Selenium might be a more suitable tool but I must admit I'm relatively new to the world of automated tests. – Adam Elsodaney Mar 17 '13 at 18:21
  • @Adam-E: Me, too! I got my symfony application working and am using it to learn how to test. At a minimum I'll end up with a baseline for future revisions. – geoB Mar 17 '13 at 23:51
  • The symfony functional test framework indeed does not execute any javascript (as it's "only" php). You would need to use some testclient which has a javascript interpreter build in or uses a browser to parse the output. A good way to start is checking out Mink, which is a layer between your code and the client doing the request. – Sgoettschkes Mar 18 '13 at 07:26
  • @Sgoettschkes: I'll accept this as an answer if it's presented as such. Let's say that Symfony is strictly (not "only") PHP. What seems to be true is that the DOM crawler is misnamed - it is solely a server-side HTML crawler because document objects can also be created by the client as in the present case. – geoB Mar 18 '13 at 22:05

4 Answers4

10

I just had the same issue and after a bit of research I found a solution which helped me. A solution to remove a field from a Collection form type This post is not exactly what you were looking for, this one is for removing an element and not adding new ones. But the principle is the same. What I did instead of $form->setValues() ... $form->getPhpValues() that I've created an array, and POSTed that

In the example bellow, the configurations field of the form is a Collection

    $submitButton = $crawler->selectButton(self::BUTTON_ADD_APPLICATION);
    $form = $submitButton->form();
    $values = array(
        'Setup' => array(
            '_token' => $form['Setup[_token]']->getValue(),
            'name'   => 'My New Setup',
            'configurations' => array(
                0 => array(
                    'country' => 'HUN',
                    'value'   => '3',
                ),
                1 => array(
                    'country' => 'GBR',
                    'value'   => '0',
                ),
            ),
        ),
    );

    $client->request($form->getMethod(), $form->getUri(), $values);

Hope it helps! And thanks for sstok for the original solution!

jkrnak
  • 956
  • 6
  • 9
  • This looks to be useful. I'm finding the documentation on behat, mink & selenium to be impenetrable. So if I can't do the full-tilt bogey on behavioral testing I'll likely do something like you propose. Thanks. – geoB Mar 23 '13 at 20:59
  • 2
    Made this my accepted answer - I was at least able to make it work for me. While not perfect, it is substantially better than not testing the ArrayCollection at all. – geoB Mar 24 '13 at 20:24
5

This can be done by calling slightly modified code from the submit() method:

// Get the form.
$form = $crawler->filter('button')->form();

// Get the raw values.
$values = $form->getPhpValues();

// Add fields to the raw values.
$values['task']['tag'][0]['name'] = 'foo';
$values['task']['tag'][1]['name'] = 'bar';

// Submit the form with the existing and new values.
$crawler = $this->client->request($form->getMethod(), $form->getUri(), $values,
    $form->getPhpFiles());

// The 2 tags have been added to the collection.
$this->assertEquals(2, $crawler->filter('ul.tags > li')->count());

The array with the news values in this example correspond to a form where you have a fields with these names:

<input type="…" name="FORM_NAME[COLLECTION_NAME][A_NUMBER][FIELD_NAME_1]" />
<input type="…" name="FORM_NAME[COLLECTION_NAME][A_NUMBER][FIELD_NAME_2]" />

With this code, the existing fields (including token) are already present in the form, that means you don't need to add all the fields.

The number (index) of the fields is irrelevant, PHP will merge the arrays and submit the data, Symfony will transform this data in the corresponding fields.

You can also remove an element from a collection:

// Get the values of the form.
$values = $form->getPhpValues();

// Remove the first tag.
unset($values['task']['tags'][0]);

// Submit the data.
$crawler = $client->request($form->getMethod(), $form->getUri(),
    $values, $form->getPhpFiles());

// The tag has been removed.
$this->assertEquals(0, $crawler->filter('ul.tags > li')->count());

Source: http://symfony.com/doc/current/book/testing.html#adding-and-removing-forms-to-a-collection

A.L
  • 10,259
  • 10
  • 67
  • 98
2

If you modify the form with javascript, you cannot test it with the symfony test framework. The reason for this is that the DomCrawler provided by symfony does only fetch the static HTML and parses it, not taking into account any manipulations which would be done by a browser with a graphical user interface (mainly javascript).

If you need to test a javascript-heavy project you need to use some framework which either uses the engine of a browser (e.g. Selenium) or which can interpret the javascript and execute all changes on the DOM (e.g. Zombie.js).

A good framework to do this is Mink, which is a layer between the testing framework and the actual client doing the request and parsing the result. It provides an API to work witha very simple PHP HTML Parser (similar to the DomCrawler used by symfony), Selenium, Zombie.js and some more.

Sgoettschkes
  • 13,141
  • 5
  • 60
  • 78
  • Thanks. I'm off now to Behat, etc., land. – geoB Mar 19 '13 at 23:28
  • Sorry about the unchecking of answer - I'm sufficiently obtuse as not to be able to make this work for me. Perhaps when there's better documentation... – geoB Mar 24 '13 at 20:23
0

I had a similar problem and in my case I realized that it was because I was not using the right crawler, I was using the one from a previous page with a slightly different form. So I think this is a good place to give the solution that worked in my case.

By getting a fresh crawler on the right web page it works

    $crawler = $client->getCrawler();
    //processing the form
    // ...

and to avoid mistakes I use the syntax

$crawler = $client->submitForm('Add client', [
            'client[members][1][fname]' => 'Benny',
            'client[members][1][dob]' => "3/12/1999",
        ]);
Ced
  • 126
  • 1
  • 4