Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 613
igorsantos07 Avatar asked Nov 11 '10 19:11

igorsantos07


People also ask

Which method is used to create a mock with PHPUnit?

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 do I run a PHPUnit test?

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.

What is a PHPUnit test?

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.

What is assertion in PHPUnit?

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 .


3 Answers

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

like image 116
netcoder Avatar answered Sep 28 '22 20:09

netcoder


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.

like image 45
ryeguy Avatar answered Sep 28 '22 20:09

ryeguy


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() );
    }
}
like image 20
Mat Lipe Avatar answered Sep 28 '22 20:09

Mat Lipe