我正在使用带有 Zend_Form 的 CSRF 隐藏哈希元素并尝试对登录进行单元测试,但不知道如何编写单元测试来包含该元素。查看文档并阅读尽可能多的教程。我什至都把它们都吃了,但没有人提到这一点。
5 回答
每次呈现表单时都会生成 Csrf 值。表单的隐藏元素会预填充该值。该值也存储在会话中。提交表单后,验证检查从表单发布的值是否存储在会话中,如果没有,则验证失败。至关重要的是,该表单必须在测试期间渲染(这样它才能生成隐藏值并将其存储到会话中),然后我们可以从渲染的 html 中提取隐藏值是什么,然后我们可以将隐藏的哈希值添加到我们的要求。考虑这个例子:
function testAddPageStoreValidData()
{
// render the page with form
$this->dispatch('/form-page');
// fetch content of the page
$html = $this->getResponse()->getBody();
// parse page content, find the hash value prefilled to the hidden element
$dom = new Zend_Dom_Query($html);
$csrf = $dom->query('#csrf')->current()->getAttribute('value');
// reset tester for one more request
$this->resetRequest()
->resetResponse();
// now include $csrf value parsed from form, to the next request
$this->request->setMethod('POST')
->setPost(array('title'=>'MyNewTitle',
'body'=>'Body',
'csrf'=>$csrf));
$this->dispatch('/form-page');
// ...
}
正确的散列存储在会话中,散列表单元素有一个 Zend_Session_Namespace 实例,其中包含散列的名称空间。
要对元素进行单元测试,您可以将元素中的 Zend_Session_Namespace 实例(使用 setSession)替换为您自己创建的包含正确散列的实例(散列存储在键“散列”中)
对于更多示例,您可能可以查看 Zend_Form_Element_Hash 类的 Zend Framework 单元测试。我想他们也不得不处理这个问题。
我在我的 Apache vhost 文件中设置了一个环境变量,它告诉代码它在哪个服务器上运行:开发、登台或生产
vhost 文件的行是:
SetEnv SITE_ENV "dev"
然后我只是让我的表单对适当的环境做出反应:
if($_SERVER['SITE_ENV']!='dev')
{
$form_element->addValidator($csrf_validator);
}
我对很多东西都使用了同样的技术。例如,如果它是开发者,我会将所有外发电子邮件重定向给我,等等。
我回答了一个与此类似的最近的问题。我也将我的答案放在这里,以防将来对任何人有所帮助。
我最近发现了一种使用散列元素测试表单的好方法。这将使用一个模拟对象来存根散列元素,您不必担心它。您甚至不必以这种方式执行 session_start 或任何操作。您也不必“预呈现”表单。
首先像这样创建一个“存根”类
class My_Form_Element_HashStub extends Zend_Form_Element_Hash
{
public function __construct(){}
}
然后,将以下内容添加到表单的某处。
class MyForm extends Zend_Form
{
protected $_hashElement;
public function setHashElement( Zend_Form_Hash_Element $hash )
{
$this->_hashElement = $hash;
return $this;
}
protected function _getHashElement( $name = 'hashElement' )
{
if( !isset( $this->_hashElement )
{
if( isset( $name ) )
{
$element = new Zend_Form_Element_Hash( $name,
array( 'id' => $name ) );
}
else
{
$element = new Zend_Form_Element_Hash( 'hashElement',
array( 'id' => 'hashElement' ) );
}
$this->setHashElement( $element );
return $this->_hashElement;
}
}
/**
* In your init method you can now add the hash element like below
*/
public function init()
{
//other code
$this->addElement( $this->_getHashElement( 'myotherhashelementname' );
//other code
}
}
set 方法实际上只是为了测试目的。在实际使用过程中您可能根本不会使用它,但现在在 phpunit 中您可以正确执行以下操作。
class My_Form_LoginTest extends PHPUnit_Framework_TestCase
{
/**
*
* @var My_Form_Login
*/
protected $_form;
/**
*
* @var PHPUnit_Framework_MockObject_MockObject
*/
protected $_hash;
public function setUp()
{
parent::setUp();
$this->_hash = $this->getMock( 'My_Form_Element_HashStub' );
$this->_form = new My_Form_Login( array(
'action' => '/',
'hashElement' => $this->_hash
}
public function testTrue()
{
//The hash element will now always validate to true
$this->_hash
->expects( $this->any() )
->method( 'isValid' )
->will( $this->returnValue( true ) );
//OR if you need it to validate to false
$this->_hash
->expects( $this->any() )
->method( 'isValid' )
->will( $this->returnValue( true ) );
}
}
您必须创建自己的存根。您不能只调用 phpunit 方法getMockObject()
,因为这将直接扩展散列元素,而普通散列元素在其构造函数中执行“邪恶”操作。
使用这种方法,您甚至不需要连接到数据库来测试您的表单!我花了一段时间才想到这一点。
如果需要,可以将setHashElement()
方法(连同变量和 get 方法)推送到某个 FormAbstract 基类中。
请记住,在 phpunit 中,您必须在表单构建期间传递哈希元素。如果你不这样做,你的init()
方法将在你的存根散列可以用 set 方法设置之前被调用,你最终将使用常规的散列元素。您会知道您使用的是常规哈希元素,因为如果您没有连接到数据库,您可能会收到一些会话错误。
让我知道您是否觉得这有帮助或者您是否使用它。
ZF2 的解决方案是在测试中创建您的表单,并从您的 csrf 表单元素中获取价值:
$form = new \User\Form\SignupForm('create-user');
$data = [
'security' => $form->get('security')->getValue(),
'email' => 'test@test.com',
'password' => '123456',
'repeat-password' => '123456',
];
$this->dispatch('/signup', 'POST', $data);