开发者

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 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());
}

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() );
    }
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜