I've created a PHP class that envelopes filter_input functions to make our developer's life easier.
To validate an HTML form with url
, 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());
}
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?
PHPUnit provides methods that are used to automatically create objects that will replace the original object in our test. createMock($type) and getMockBuilder($type) methods are used to create mock object. The createMock method immediately returns a mock object of the specified type.
How to Run Tests in PHPUnit. You can run all the tests in a directory using the PHPUnit binary installed in your vendor folder. You can also run a single test by providing the path to the test file. You use the --verbose flag to get more information on the test status.
PHPUnit is a unit testing framework for the PHP programming language. It is an instance of the xUnit architecture for unit testing frameworks that originated with SUnit and became popular with JUnit. PHPUnit was created by Sebastian Bergmann and its development is hosted on GitHub.
The assertion methods are declared static and can be invoked from any context using PHPUnit\Framework\Assert::assertTrue() , for instance, or using $this->assertTrue() or self::assertTrue() , for instance, in a class that extends PHPUnit\Framework\TestCase .
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() );
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With