Matt
Matt

Reputation: 7040

Simulating HTTP Request for Unit Testing

I'm writing an MVC framework that utilizes a library which will help manage input variables (e.g. get, post, etc.).

Right now, I'm testing the POST and GET variables by forcing their values into the $_POST and $_GET superglobals, respectively, but I've reached a roadblock when testing for PUT and DELETE request variables.

Example:

public function testGET()
{
    $_GET['test'] = 'test value';

    $get_value = Input::get('test', '[default value]');

    assertEquals('test value', $get_value);
}

Is there a good way to mock/simulate these variables or to simulate an HTTP request using the various methods available (specifically, GET, POST, PUT, DELETE)?

Upvotes: 3

Views: 5214

Answers (2)

b01
b01

Reputation: 4384

Request headers are part of the PHP super global $_SERVER. You can set the appropriate header in the $_SERVER. For example:

<?php
    $_SERVER['REQUEST_METHOD'] = 'PUT';
    // Now you should be able to pull data 
?>

See PHP $_SERVER manual. http://php.net/manual/en/reserved.variables.server.php

Upvotes: -4

Darren Cook
Darren Cook

Reputation: 28913

I sometimes feels people over-use Design For Testability, ending up with code where 80% of the complexity seems to be just to make it easier for testing. I feel it is often better to switch to functional testing for stuff that is getting messy to unit test.

But for the PHP super-globals I give in. So I try to avoid using $_POST or $_GET in any framework code, and instead use a parameter or, if that makes the code too ugly, then a class member or static member.

So, your get() function could be rewritten like this:

public static function get($name,$default,$GET=null)
{
  if($GET===null)$GET=$_GET;  //For non-test code
  if(!array_key_exists($name,$GET))return $default;
  return $GET[$name];
}

Then your test becomes:

public function testGET()
{
    $GET=array('test' => 'test value');
    $get_value = Input::get('test', '[default value]', $GET);
    $this->assertEquals('test value', $get_value);
}

This quickly gets awkward when the call to Input::get() is buried deep in a call hierarchy; then a static class member is better. To use a class member, $GET can be passed in an init() function. But if having an init() function is inappropriate, it can be done like this:

public static $GET=null;

public static function get($name,$default)
{
  if(self::$GET===null)self::$GET=$_GET;  //Init on first call
  if(!array_key_exists($name,self::$GET))return $default;
  return $GET[$name];
}

And then the test becomes:

public function testGET()
{
    Input::$GET=array('test' => 'test value');
    $get_value = Input::get('test', '[default value]');
    $this->assertEquals('test value', $get_value);
}

Aside: another (non-testing) reason I like this approach is that it allows a client-code to choose to use $_GET,$_POST, $_REQUEST, etc. So I would name the static variable self::$INPUT, and have an init() function where the user is expected to pass in $_POST or $_GET or whatever they want.

The second half of your question asks about testing PUT and DELETE data. However you didn't say how it is being processed in the real code. Do you have some code similar to that shown in this answer: https://stackoverflow.com/a/5374881/841830

If so, one approach is to mock the getContent() function, shown there, to return hard-coded text, instead of getting it from php://input. Another approach is to have another function (in your real class), principally for testing, that goes in and sets $this->content before the first call that will use that variable. ($this->content in that answer is basically like the self::$INPUT variable I mentioned earlier.)

Upvotes: 5

Related Questions