Symfony2 phpunit功能测试自定义用户身份验证失败后重定向(会话相关)

时间:2015-03-16 17:06:54

标签: symfony session authentication phpunit functional-testing

简介:能够使用和Wordpress用户的自定义用户实现:

在我们的项目中,我们已经为相应的自定义用户( WordpressUser实现UserInterface,EquatableInterface )实现了一个自定义用户提供程序(用于Wordpress用户 - 实现UserProviderInterface )。我在security.yml中设置了防火墙并实施了几个选民。

# app/config/security.yml
security:
    providers:
        wordpress:
            id: my_wordpress_user_provider

    firewalls:
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false
        default:
            anonymous: ~
            http_basic: ~
            form_login:
                login_path: /account

功能性phpunit测试:

到目前为止一直很好 - 但现在是棘手的部分:在功能性phpunit测试中模拟经过身份验证的(Wordpress)用户。我成功地模拟了WordpressUserProvider,因此在loadUserByUsername(..)上将返回一个模拟的WordpressUser。在我们的BaseTestCase(扩展WebTestCase)中,模拟的WordpressUser得到验证,令牌存储到会话中。

//in: class BaseTestCase extends WebTestCase

/**
 * Login Wordpress user
 * @param WordpressUser $wpUser
 */
private function _logIn(WordpressUser $wpUser)
{
    $session = self::get('session');

    $firewall = 'default';
    $token = new UsernamePasswordToken($wpUser, $wpUser->getPassword(), $firewall, $wpUser->getRoles());
    $session->set('_security_' . $firewall, serialize($token));
    $session->save();

    $cookie = new Cookie($session->getName(), $session->getId());
    self::$_client->getCookieJar()->set($cookie);
}

问题:丢失新请求的会话数据:

简单测试在认证部分成功。直到使用重定向测试。用户仅通过一次请求进行身份验证,并且“忘记了”#39;重定向后。这是因为Symfony2测试客户端将在每个请求上关闭()并引导()内核,这样,会话就会丢失。

变通方法/解决方案:

question 12680675中提供的解决方案中,只有用户ID才能用于UsernamePasswordToken(..)来解决此问题。我们的项目需要完整的用户对象。

Unable to simulate HTTP authentication in functional test中提供的解决方案中,使用了基本的HTTP身份验证。在这种情况下,不能使用完整的用户对象 - 包括角色。

根据Isolation of tests in Symfony2的建议,您可以通过覆盖测试客户端中的doRequest()方法来持久化实例。正如所建议的,我已经创建了一个自定义测试客户端并对doRequest()方法进行了覆盖。

自定义测试客户端,以便'存储'请求之间的会话数据:

namespace NS\MyBundle\Tests;

use Symfony\Bundle\FrameworkBundle\Client as BaseClient;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

/**
 * Class Client
 * Overrides and extends the default Test Client
 * @package NS\MyBundle\Tests
 */
class Client extends BaseClient
{
    static protected $session;

    protected $requested = false;

    /**
     * {@inheritdoc}
     *
     * @param Request $request A Request instance
     *
     * @return Response A Response instance
     */
    protected function doRequest($request)
    {
        if ($this->requested) {
            $this->kernel->shutdown();
            $this->kernel->boot();
        }

        $this->injectSession();
        $this->requested = true;

        return $this->kernel->handle($request);
    }

    /**
     * Inject existing session for request
     */
    protected function injectSession()
    {
        if (null === self::$session) {
            self::$session = $this->getContainer()->get('session');
        } else {
            $this->getContainer()->set('session', self::$session);
        }
    }
}

如果没有持有shutdown()和boot()调用的if语句,这个方法或多或少都有效。有一些奇怪的问题,无法找到$ _SERVER索引键,所以我想为系统的其他方面正确地重新实例化内核容器。在保留if语句的同时,用户无法进行身份验证,但会话数据在请求之前和期间/之后是相同的(由var_export检查到日志)。

问题(S):

我在这种导致身份验证失败的方法中缺少什么?身份验证(和会话检查)是直接在内核启动(/)之后完成的,还是我错过了其他内容?有没有人有其他/更好的解决方案来保持会话完好无损,以便用户在功能测试中进行身份验证?提前感谢您的回答。

- 编辑 -

此外:测试环境的会话存储设置为session.storage.mock_file。这样,会话应该已经存在于Symfony2组件here描述的请求之间。在(第二个)请求之后在测试中检查时,会话似乎完好无损(但在某种程度上被身份验证层忽略了?)。

# app/config/config_test.yml
# ..
framework:
    test: ~
    session:
        storage_id: session.storage.mock_file
    profiler:
        collect: false

web_profiler:
    toolbar: false
    intercept_redirects: false
# ..

1 个答案:

答案 0 :(得分:1)

我的假设很接近;这不是没有坚持的会议,问题在于嘲笑服务已被删除'由内核提出新的要求。这是功能性phpunit测试的基本行为......

我发现在Symfony \ Component \ Security \ Http \ Firewall \ AccessListener中进行调试时,这个问题一定是个问题。在那里找到了令牌,而且(不再是)嘲笑的自定义WordpressUser就在那里 - 空了。这解释了为什么只设置用户名而不是用户对象在上面提到的建议的解决方法中工作(不需要模拟的User类)。

<强>解决方案

首先,您不需要像我上面的问题中所建议的那样覆盖客户端。为了能够持久化您的模拟类,您必须扩展AppKernel并使用闭包作为参数进行某种内核修饰符覆盖。有一个解释here on LyRiXx Blog。注入一个闭包后,您可以在请求后恢复服务模拟。

// /app/AppTestKernel.php

/**
 * Extend the kernel so a service mock can be restored into the container
 * after a request.
 */

require_once __DIR__.'/AppKernel.php';

class AppTestKernel extends AppKernel
{
    private $kernelModifier = null;

    public function boot()
    {
        parent::boot();

        if ($kernelModifier = $this->kernelModifier) {
            $kernelModifier($this);
        };
    }

    /**
     * Inject with closure
     * Next request will restore the injected services
     *
     * @param callable $kernelModifier
     */
    public function setKernelModifier(\Closure $kernelModifier)
    {
        $this->kernelModifier = $kernelModifier;
    }
}

用法(在您的功能测试中):

$mock = $this->getMockBuilder(..);
..
static::$kernel->setKernelModifier(function($kernel) use ($mock) {
    $kernel->getContainer()->set('bundle_service_name', $mock);
});

我仍然需要调整类和扩展的WebTestCase类,但这似乎对我有用。我希望我可以用这个答案指出别人在正确的(?)方向。