首页 文章

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

提问于
浏览
2

Intro: Custom user implementation to be able to use and Wordpress users:

在我们的项目中,我们为相应的自定义用户(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

Functional phpunit testing:

到目前为止一切都很好 - 但现在是棘手的部分:在功能性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);
}

The problem: losing session data on new request:

简单测试在认证部分成功 . 直到使用重定向测试 . 用户仅对一个请求进行身份验证,并在重定向后进行'forgotten' . 这是因为Symfony2测试客户端将在每个请求上关闭()并引导()内核,这样, session 就会丢失 .

解决方法/解决方案:

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

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

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

Custom test client to 'store' session data between requests:

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检查到日志) .

Question(s):

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

--EDIT--

此外:测试环境的会话存储设置为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 回答

  • 1

    我的假设很接近;它不是没有持久化的会话,问题是内核在新的请求下“擦除”了模拟服务 . 这是功能性phpunit测试的基本行为......

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

    Solution

    首先,您不需要像我上面的问题中建议的那样覆盖客户端 . 为了能够持久化您的模拟类,您必须扩展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类,但这似乎对我有用 . 我希望我能用这个答案指出别人在正确(?)方向 .

相关问题