How to make a PHPUnit test that depends on ~real~ POST/GET data?

瘦欲@ 提交于 2019-12-22 04:45:16

问题


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?


回答1:


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




回答2:


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.




回答3:


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


来源:https://stackoverflow.com/questions/4158307/how-to-make-a-phpunit-test-that-depends-on-real-post-get-data

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!