在测试中获取CSRF令牌," CSRF令牌无效" - 功能性ajax测试

时间:2014-06-29 23:05:33

标签: ajax symfony csrf

我试图在Symfony2中测试ajax请求。我正在编写一个单元测试,它会在我的app/logs/test.log中抛出以下错误:

request.CRITICAL: Uncaught PHP Exception Twig_Error_Runtime: 
"Impossible to access an attribute ("0") on a string variable 
("The CSRF token is invalid. Please try to resubmit the form.")
in .../vendor/twig/twig/lib/Twig/Template.php:388

我的代码非常简单。

public function testAjaxJsonResponse()
{
    $form['post']['title'] = 'test title';
    $form['post']['content'] = 'test content';
    $form['post']['_token'] = $client->getContainer()->get('form.csrf_provider')->generateCsrfToken();

    $client->request('POST', '/path/to/ajax/', $form, array(), array(
        'HTTP_X-Requested-With' => 'XMLHttpRequest',
    ));

    $response = $client->getResponse();
    $this->assertSame(200, $client->getResponse()->getStatusCode());
    $this->assertSame('application/json', $response->headers->get('Content-Type'));
}

问题似乎是CSRF令牌,我可以为测试禁用它,但我真的不想这样做,我让它通过发出2个请求(第一个加载)带有表单的页面,我们抓住_token并使用XMLHttpRequest发出第二个请求 - 这显然看起来相当愚蠢和低效!

1 个答案:

答案 0 :(得分:7)

解决方案

我们可以使用以下内容为CSRF请求生成自己的ajax令牌:

$client->getContainer()->get('form.csrf_provider')->generateCsrfToken($intention);

此处变量$intention指的是Form Type Options中设置的数组键。

添加intention

Form Type中,您需要添加intention密钥。 e.g:

# AcmeBundle\Form\Type\PostType.php

/**
 *  Additional fields (if you want to edit them), the values shown are the default
 * 
 * 'csrf_protection' => true,
 * 'csrf_field_name' => '_token', // This must match in your test
 *
 * @param OptionsResolverInterface $resolver
 */
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
    $resolver->setDefaults(array(
        'data_class' => 'Acme\AcmeBundle\Entity\Post',
        // a unique key to help generate the secret token
        'intention' => 'post_type',
    ));
}

Read the documentation

在功能测试中生成CSRF令牌

现在我们有intention,我们可以在单元测试中使用它来生成有效的CSRF令牌。

/**
 * Test Ajax JSON Response with CSRF Token
 * Example uses a `post` entity
 *
 * The PHP code returns `return new JsonResponse(true, 200);`
 */
public function testAjaxJsonResponse()
{
    // Form fields (make sure they pass validation!)
    $form['post']['title'] = 'test title';
    $form['post']['content'] = 'test content';

    // Create our CSRF token - with $intention = `post_type`
    $csrfToken = $client->getContainer()->get('form.csrf_provider')->generateCsrfToken('post_type');
    $form['post']['_token'] = $csrfToken; // Add it to your `csrf_field_name`

    // Simulate the ajax request
    $client->request('POST', '/path/to/ajax/', $form, array(), array(
        'HTTP_X-Requested-With' => 'XMLHttpRequest',
    ));

    // Test we get a valid JSON response
    $response = $client->getResponse();
    $this->assertSame(200, $client->getResponse()->getStatusCode());
    $this->assertSame('application/json', $response->headers->get('Content-Type'));

    // Assert the content
    $this->assertEquals('true', $response->getContent());
    $this->assertNotEmpty($client->getResponse()->getContent());
}