14

Having some issues using PHPUnit to test my controllers.

Code I was working on so far was implementing $_POST or other request variables:

$_SERVER['REQUEST_METHOD'] = 'POST';
$_POST = array(
    'test' => true
);

Most of tests worked perfectly this way until I run into methods that take uses of filter_input_array function:

$_SERVER['REQUEST_METHOD'] = 'POST';
$_REQUEST = $_POST = $GLOBALS['_POST'] = array(
    'test' => true
);

// ....

var_dump(filter_input_array(INPUT_POST));

NULL

I'm not willing to remove filter_input functions from not mine code, but I'm unable to make them working in tests.

Versionings:
PHP 5.5.9-1ubuntu4.9 (cli) (built: Apr 17 2015 11:44:57)
Apache/2.4.7 (Ubuntu)
PHPUnit 4.6.6 by Sebastian Bergmann and contributors.

Any help will be appreciated.

EDIT 2015.05.11

Setting $_SERVER with CONTENT_LENGTH and CONTENT_TYPE does not fix the problem. My version of PHP does not allow me to write to php://stdin in way its described in PHP 5.6.0 chagelog (or way I understand it), but file_put_contents(STDIN,..) succeed but does not work anyway.

Because it is a phpunit test, maybe there is some kind of annotation or phpunit.xml entry I don't know yet, that may fix this problem in php-cgi POST setting manner.

Community
  • 1
  • 1
yergo
  • 4,761
  • 2
  • 19
  • 41
  • Your two examples assign `$_POST` differently. What are the values of the three variables `$_REQUEST`, `$_POST` and `$GLOBALS['_POST']` *before* you do `$_REQUEST = $_POST = $GLOBALS['_POST']`? – Markus Amalthea Magnuson May 17 '15 at 10:24
  • Their empty arrays. As it is a phpunit test, it runs in command line (php-cli) so it is not even going though web server and no `$_REQUEST`-related vars should be set anyway. I was looking at unit tests of php itself, how they test `filter_input_array` but it did not helped me at all. – yergo May 17 '15 at 12:51
  • take a look at http://stackoverflow.com/questions/5655284/how-to-pass-parameters-from-command-line-to-post-in-php-script it may help ? – exussum May 22 '15 at 14:49

4 Answers4

14

If the input to filter_input_array can only be set by the initial request, and not changed at run time, then the only way to still test it is to have a your base test proxy to another test script by making an HTTP request with the right POST data and processing the response.

main_test.php:

<?php
$data = array(
    'testname' => 'yourtestname',
    'some_post_var' => 'some_value'
);

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "http://localhost/proxy_test.php");
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

$response = curl_exec($ch);

curl_close($ch);

if ($response == 'pass') {
  // test passed
}

proxy_test.php

<?php
$test_name $_POST['testname']; // what test to run?
$response = run_test($test_name); // run test that uses filter_input_array
if ($response) {
    echo "pass"; // used by main_test.php to check if test passed
} else {
   echo "fail";
}
Scott Jungwirth
  • 6,105
  • 3
  • 38
  • 35
  • Thumbs up for suggesting `curl`. It's workaround, but does seem to work. Also, can you edit your proxy_test( `$test_name =`) ? – Alex Tartan May 21 '15 at 07:15
  • Looks like a way to do this. Will try. Unfortunately it probably won't provide code coverage in configuration I have curretly, but for now can live w/o that. – yergo May 21 '15 at 08:33
7

It seems like this is a limitation of PHP, filter_input_array() does not allow a $_POST array modified at runtime. See this bug for some more information. The workaround is probably to use one of the other filter functions and pass in the $_POST array yourself.

Markus Amalthea Magnuson
  • 8,415
  • 4
  • 41
  • 49
  • Seems like providen bug still exists, as function is returning NULL instead of false or empty array. Anyway, I think that it may be possible to run this test in phpunit, as this tool provides running scripts in separate processes, and I provided an SO answer that proofs that setting POST on script startup is possible. I just have not found a workaround for my case. – yergo May 17 '15 at 19:38
1

If none of the arguments is set, this function returns NULL, not an array of NULL values.

// No POST vars set in request

$_POST = array();
$args = array('some_post_var' => FILTER_VALIDATE_INT);
$myinputs = filter_input_array(INPUT_POST, $args);
var_dump($myinputs);

Expected Output: array(1) { ["some_post_var"]=> NULL } 

Actual Output: NULL

While filtering input arrays, be careful of what flags you set besides FILTER_REQUIRE_ARRAY. For example, setting the flags like so:

 <?php 
    $filter = array( 
    'myInputArr' => array('filter' => FILTER_SANITIZE_STRING, 
                          'flags' => array('FILTER_FLAG_STRIP_LOW', 'FILTER_REQUIRE_ARRAY')) 
    ); 

$form_inputs = filter_input_array(INPUT_POST, $filter); 
?> 

.. will result in a blank $form_inputs['myInputArr'] regardless of what $_POST['myInputArr'] contains.

Saty
  • 22,443
  • 7
  • 33
  • 51
1

One approach to this is to use a helper method to run your filter_input_array inside of then mock this method during tests to use something else like filter_var_array.

For example this use case could be accomplished by doing:

class Data_Class {

    protected function i_use_filters() {
        return $this->filter_input_array();
    }


    protected function filter_input_array() {
        return filter_input_array( INPUT_POST );
    }
}

class Test_Class extends TestCase {

    public function test_filters() : void {
        $mock = $this->getMockBuilder( Data_Class::class )
                     ->setMethods( [ 'filter_input_array' ] )
                     ->getMock();
        $mock->method( 'filter_input_array' )
             ->willReturnCallback( function () {
                 if ( ! isset( $_POST ) ) {
                     return null;
                 }
                 return \filter_var_array( $_POST );
             } );

        $_POST['test'] = true;
        $this->assertTrue( $mock->i_use_filters()['test'] );
    }
}
Mat Lipe
  • 725
  • 8
  • 14