How to make a PHPUnit test that depends on ~real~ POST/GET data?
I've created a PHP class that envelopes filter_input functions to make our developer's life easier.
To validate an HTML form withurl
, name
and age
fields, the code would be like that:
$post = Filter::POST();
if ($post->validate_string('name') && $post->validate_integer('age')) {
$url = $post->sanitize_url('url');
}
It would be the same as:
if (filter_input(INPUT_POST,'name',FILTER_UNSAFE_RAW) && filter_input(INPUT_POST,'age',FILTER_VALIDATE_INTEGER)) {
$url = filter_input(INPUT_POST,'url',FILTER_SANITIZE_URL);
}
Well, I think the code is done and now I would like to create a PHPUnit test for it.
The problem is that I have no idea on how开发者_如何转开发 to fake GET/POST data on a PHPUnit method, not for this case.
I don't need to insert values in$_POST
, I need "real" data on it, because filter_input
works with the data the script received, not with the actual $_POST
superglobal.
I've tried using the following PHPT test and PHPUnit method to achieve this, with no success at all:
--TEST--
Generates POST and GET data to be used in FilterTest.php
--POST--
name=Igor&age=20
--GET--
name=Igor&age=19
--FILE--
<?php
echo $_POST['nome'].' = '.$_POST['idade'];
?>
--EXPECT--
Igor = 20
public function testPhpt() {
$phpt = new PHPUnit_Extensions_PhptTestCase('FilterTest_data.phpt', array('cgi' => 'php-cgi'));
$result = $phpt->run();
$this->assertTrue($result->wasSuccessful());
}
EDIT
Original code: http://pastebin.com/fpw2fpxM
Code used for initial testing: http://pastebin.com/vzxsBQWm (sorry for the portuguese, I know it would be better to code in english, but it's how things work here where I work. If you really think it's really needed, I can translate the code).Any idea on what can I do to test this class?
You can't fake raw POST data. But the problem lies in your code: it's not unit-testable. Instead of:
if (filter_input(INPUT_POST,'name',FILTER_UNSAFE_RAW) && filter_input(INPUT_POST,'age', FILTER_VALIDATE_INTEGER)) {
$url = filter_input(INPUT_POST,'url',FILTER_SANITIZE_URL);
}
If you had:
if (filter_var($data['name'], FILTER_UNSAFE_RAW) && filter_var($data['age'], FILTER_VALIDATE_INT)) {
$url = filter_var($data['url'], FILTER_SANITIZE_URL);
}
// where $data is a copy of $_POST in that case
Would render your code unit testable and amount to the same thing as your previous code did.
P.S.: FILTER_VALIDATE_INTEGER is not valid. The proper constant for this is FILTER_VALIDATE_INT
There are 2 problems with your code. One is that you're accessing global variables, which are hard to test. The second is you're tightly binding the class to specific data (post, get, etc).
What you should do is make the class satisfy this kind of interface:
$filter = new Filter($_POST);
$filter->validate_string('name');
The benefits should be obvious. You don't have to use $_POST
or $_GET
or any other predefined type as inputs. Not only can you now validate input from any source (since you just pass it into the constructor), more importantly, you can inject any data into there you like for the purpose of testing.
Woops, I missed the part about using filter_input
. The solution is to use filter_var
instead. It allows you to run the filters on any variable.
One approach to this is to use a helper method to run your filter_input
inside of then mock this method during tests to use something else like filter_var
.
For example this use case could be accomplished by doing:
class Data_Class {
protected function i_use_filters() {
if ( $this->filter_input( 'name', FILTER_UNSAFE_RAW ) && $this->filter_input( 'age', FILTER_VALIDATE_INT ) ) {
$url = $this->filter_input( 'url', FILTER_SANITIZE_URL );
}
return $url;
}
protected function filter_input( $name, $filter ) {
return filter_input( INPUT_POST, $name, $filter );
}
}
class Test_Class extends TestCase {
public function test_filters() : void {
$mock = $this->getMockBuilder( Data_Class::class )
->setMethods( [ 'filter_input' ] )
->getMock();
$mock->method( 'filter_input' )
->willReturnCallback( function ( $name, $filter ) {
if ( ! isset( $_POST[ $name ] ) ) {
return null;
}
return filter_var( $_POST[ $name ], $filter );
} );
$_POST[ 'name' ] = 'Myself';
$_POST[ 'age'] = 40;
$_POST[ 'url' ] = 'https://test.com';
$this->assertEquals( 'https://test.com', $mock->i_use_filters() );
}
}
精彩评论