I am writing unit tests for several methods which return HTTP response codes. I cannot find a way to assert an HTTP response code. Perhaps I am missing something obvious, or I am misunderstanding something about PHPUnit.
I am using PHPUnit 4.5 stable.
Relevant part of class Message:
public function validate() {
// Decode JSON to array.
if (!$json = json_decode($this->read(), TRUE)) {
return http_response_code(415);
}
return $json;
}
// Abstracted file_get_contents a bit to facilitate unit testing.
public $_file_input = 'php://input';
public function read() {
return file_get_contents($this->_file_input);
}
Unit test:
// Load invalid JSON file and verify that validate() fails.
public function testValidateWhenInvalid() {
$stub1 = $this->getMockForAbstractClass('Message');
$path = __DIR__ . '/testDataMalformed.json';
$stub1->_file_input = $path;
$result = $stub1->validate();
// At this point, we have decoded the JSON file inside validate() and have expected it to fail.
// Validate that the return value from HTTP 415.
$this->assertEquals('415', $result);
}
PHPUnit returns:
1) MessageTest::testValidateWhenInvalid
Failed asserting that 'true' matches expected '415'.
I'm unsure why $result is returning 'true' . . . especially as a string value. Also unsure what my 'expected' argument ought to be.
According to the docs you can call the http_response_code() method with no parameters to receive the current response code.
<?php
http_response_code(401);
echo http_response_code(); //Output: 401
?>
Therefore your test should look like:
public function testValidateWhenInvalid() {
$stub1 = $this->getMockForAbstractClass('Message');
$path = __DIR__ . '/testDataMalformed.json';
$stub1->_file_input = $path;
$result = $stub1->validate();
// At this point, we have decoded the JSON file inside validate() and have expected it to fail.
// Validate that the return value from HTTP 415.
$this->assertEquals(415, http_response_code()); //Note you will get an int for the return value, not a string
}
Related
I wonder how to use sequence in HTTP::fake call back.
What I want is to get a sequence of responses when the body of my request contains ListSupplierRoutes.
here is my code:
Http::fake(function (Request $request) {
$body = $request->body();
$xmlFileName = 'login';
if (Str::contains($body, 'Login')) {
$xmlFileName = 'login';
}
if (Str::contains($body, 'ListSupplierRoutes')) {
return Http::sequence()
->push($this->loadXMLResponse('list-supplier-routes-ryanair'))
->push($this->loadXMLResponse('list-supplier-routes-ezy'));
}
// Some other conditions
return Http::response($this->loadXMLResponse($xmlFileName));
});
With this approach I get the below exception:
BadMethodCallException : Method Illuminate\Http\Client\ResponseSequence::then does not exist.
/..../vendor/laravel/framework/src/Illuminate/Macroable/Traits/Macroable.php:103
.../vendor/laravel/framework/src/Illuminate/Http/Client/PendingRequest.php:730
.../vendor/laravel/framework/src/Illuminate/Http/Client/PendingRequest.php:707
.../vendor/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php:64
.../vendor/guzzlehttp/guzzle/src/Middleware.php:37
.../vendor/guzzlehttp/guzzle/src/RedirectMiddleware.php:71
.../vendor/guzzlehttp/guzzle/src/Middleware.php:61
.../vendor/guzzlehttp/guzzle/src/HandlerStack.php:75
.../vendor/guzzlehttp/guzzle/src/Client.php:331
.../vendor/guzzlehttp/guzzle/src/Client.php:168
.../vendor/guzzlehttp/guzzle/src/Client.php:187
.../vendor/laravel/framework/src/Illuminate/Http/Client/PendingRequest.php:609
.../vendor/laravel/framework/src/Illuminate/Support/helpers.php:234
.../vendor/laravel/framework/src/Illuminate/Http/Client/PendingRequest.php:624
.../vendor/laravel/framework/src/Illuminate/Http/Client/PendingRequest.php:528
Make sure your $this->loadXMLResponse() is returning either a string or an array. Also, the Http::fake() requires that you pass an associative array of url mapping to Http::resonse() type or Http::sequence().
For instance
Http::fake(['http://stackoverflow.com' => Http::sequence()->push('OK', 200)->push('Bad Request', 400)])
I dont know whether the question (the way I asked) is correct or not. I'm open for your suggestion. I want to know how exactly the following code works. If you want any details I can provide as much I want.
public function processAPI() {
if (method_exists($this, $this->endpoint)) {
return $this->_response($this->{$this->endpoint}($this->args));
}
return $this->_response("No Endpoint: $this->endpoint", 404);
}
private function _response($data, $status = 200) {
header("HTTP/1.1 " . $status . " " . $this->_requestStatus($status));
return json_encode($data);
}
private function _requestStatus($code) {
$status = array(
200 => 'OK',
404 => 'Not Found',
405 => 'Method Not Allowed',
500 => 'Internal Server Error',
);
return ($status[$code])?$status[$code]:$status[500];
}
/**
* Example of an Endpoint
*/
protected function myMethod() {
if ($this->method == 'GET') {
return "Your name is " . $this->User->name;
} else {
return "Only accepts GET requests";
}
}
Here $this->endpoint is 'myMethod' (a method I want to execute)
I pass the method which I want to execute in the url. The function catches the request process then call the exact method. I want to how it's works. Especially this line.
return $this->_response($this->{$this->endpoint}($this->args));
PHP Supports both variable functions and variable variables.
When it reaches you statement within processApi
return $this->_response($this->{$this->endpoint}($this->args));
PHP will resolve your endpoint variable, we'll replace it with myMethod which is in your example:
return $this->_response($this->myMethod($this->args));
As you can see, we're now calling a method which exists on your class. If you set the endpoint to something which didn't exist, it would throw an error.
If myMethod returns a string such as my name is bob then once $this->myMethod($this->args) executes PHP will resolve that value as the argument for $this->_response() resulting in:
return $this->_response('my name is bob');
Following that chain of events, the processAPI() method will finally return that string JSON encoded as that's what the _response method does.
I've been stuck on this for a while and I'm not sure why PHPunit can't see that the function is being called.
This is the code I'm trying to test:
public function handle()
{
$path = $this->request->getPath();
$requestMethod = $this->request->getMethod();
if (!$path) {
$this->redirect('home');
} else if (!$this->isMethodPathFound($path, $requestMethod)) {
$this->redirect('404');
} else {
$handler = $this->getControllerFullName($this->routes[$path]['handler']);
if (is_callable($handler)) {
call_user_func($handler);
} else {
$this->redirect('404');
}
}
}
/**
* #param string $path
* #param int $statusCode
*/
public function redirect($path, $statusCode = 303)
{
if (defined('TESTING_ENVIRONMENT') && TESTING_ENVIRONMENT) {
return;
}
header(
'Location: ' . $this->request->getProtocol() .
$this->request->getHost() . '/' . $path,
true,
$statusCode
);
die();
}
The TESTING_ENVIRONMENT variable is set for the header function so it does not trigger on running PHPunit (I don't want to create another class to have that redirect function just to be able to mock it for one test) and this is the testing code:
public function testHandlePathIsEmpty()
{
$requestMock = $this->getMockBuilder('\services\Request')->getMock();
$requestMock->expects($this->once())->method('getPath')->willReturn('');
$requestMock->expects($this->once())->method('getMethod')->willReturn('GET');
$routerMock = $this->getMockBuilder('\services\Router')
->setConstructorArgs([$this->routes, $requestMock])
->enableProxyingToOriginalMethods()
->getMock();
$routerMock->expects($this->once())->method('redirect')
->with('asdasd')->willReturn(true);
$routerMock->handle();
}
The $routerMock object should definitely invoke the "redirect" function, and it says that it does not get invoked..even though when I var_dump/die inside the function, it does go inside of it.
Thanks for the help!
Though you hesitated to show the complete output of phpunit's error, your problem is very likely not that your method is not called, but that it is not called with all the expectations you defined.
Your code
$routerMock->expects($this->once())->method('redirect')
->with('asdasd')->willReturn(true);
translates to the following expectations: The method redirect must be called exactly once with an argument 'asdasd' and will return true.
From your testcode I do not see that there is asdasd passed to the redirect method. Your test will most likely succeed when you remove the with expectation.
Just to make this clear. If you have to mock the class u want to test, your code is way to complex and you should think about implementing your logic in another way.
How about not mocking the class you are actually testing, create the new instance by passing the Request and a Router Mock (Router mock might not have any logic since you are not going to use it) and then do the following in your code:
public function handle()
{
$request = $this->request;
$path = $request->getPath();
if (!$path) {
$this->redirect('home');
} else if (!$this->isMethodPathFound($path, $request->getMethod())) {
$this->redirect('404');
} else {
$handler = $this->getControllerFullName($this->routes[$path]['handler']);
if (is_callable($handler)) {
call_user_func($handler);
} else {
$this->redirect('404');
}
}
}
In your Unit-Test, you now can just test for
$requestMock
->expects($this->never())
->method('getMethod');
I see that this would only cover the second case to not being executed but the third one could happen aswell. Thats always a point why your code is not clean enough.
You should read something about KISS and SOLID to make your code more testable. This method is just too complex as you could test it correctly.
I'm using PHP 5.3, and trying to develop a simple web service that gets some parameters with POST method and has a response.
function start(){
getAndValidateParams();
global $response;
echo json_encode($response);
}
function getAndValidateParams(){
// token (mandatory)
if(isset($_POST[PARAM_TOKEN])){
echo 'got your token';
}else{
$response[ERROR_CODE] = ERR2_INVALID_TOKEN;
$response[DESCRIPTION] = CODE2_DESC;
}
}
I'm trying to test that with Postman:
The problems:
1. About the Xdebug HTML I saw the following question, If I turn the var_dump off, will it disable usage of var_dump() inside my php code? (I want to be able use it for debugging but not seeing that in the response).
2.Also I have a problem to pass the parameter 'token', I don't see it in getAndValidateParams().
Any help will be appreciated.
I have used your function to just get insight in this and for tesing you can use also there is Advanced REST client in chrome similar to postMAN that you are using --
use the below lines to debug this --
function start(){
$response = getAndValidateParams();
return json_encode($response);
}
// calling function ends here
// statrt another function that is being called
function getAndValidateParams(){
// token (mandatory)
// print_r($_POST);die; // just for debug purpose
if(isset($_POST[PARAM_TOKEN])){
$response[ERROR_CODE] = 0;
$response[DESCRIPTION] = "Success";
$response[DEtail] = $yourdetailarr; // array of data that you want to retuen
}else{
$response[ERROR_CODE] = ERR2_INVALID_TOKEN;
$response[DESCRIPTION] = CODE2_DESC;
}
return $response;
}
/// ends here
check the response here by calling start function .
I am using xmlprc server in codeignter for web services . the flow of my application is that i need to pass parameters to the xmlrpc server method which then should invoke another controller class method which would set the parameters in a js function and that js method is invoked concurrently .
The problem i am facing is in calling the controller class method from the xmlrpc server method and getting the response to the server parent method which could then be fetched using xmlhttprequest.
my xmlrpc server method is:
function update_p($request) {
$parameters = $request->output_parameters();
$this->session->set_userdata(array("portfolio" =>$parameters['0']["portfolio"]));
$this->session->set_userdata(array("filter" =>$parameters['0']["filter"]));
$url = base_url("ControllerClass/update_p?".$parameters['0']["portfolio"].'&'.$parameters['0']["filter"]);
header("Location: $url");
$xml_rpc_rows=array("portfolio"=>$parameters['0']["portfolio"],"filter"=>$parameters['0']["filter"]);
$response = array(
$xml_rpc_rows,
'struct');
$this->xmlrpc->send_response($response);
}
Controller Class method:
public function update_p() {
$loginid = $this->session->userdata('loginid');
if(!isset($loginid)){
die;
}
error_reporting(E_ERROR);
if (time()>$this->session->userdata('expire')) { redirect("/dashboard/logout?expired=Y","location",401); die; }
$out='';
$request="USER ".$loginid.($this->session->userdata('isMobile')?"#mobile":"")."\n";
if(isset($_GET["portfolio"])) {
$portfolio=trim($_GET["portfolio"]);
$request.='ECHO "LISTP":'."\nLISTP0 #".$portfolio;
if(isset($_GET["filter"])) {
$filter=trim($_GET["filter"]);
$request.=" -".$filter;
}
if(isset($_GET["sort"])) {
$sort=trim($_GET["sort"]);
if ($sort>=1024) $request.=" -s".($sort&1023);
else $request.=" -S".$sort;
}
$ph = isset($_GET["first"]);
if ($ph) {
$this->load->model('Model');
$resultArray = $this->Model->getData($this->session->userdata('loginid'),$this->session->userdata('isMobile')?'mobile':'default','listp');
$request.=" ".$resultArray[0]['listp'];
}
$request.="\nECHO ,\n";
if(isset($_GET["watch"])) {
$portfolio=trim($_GET["watch"]);
if ($ph)
$resultArray = $this->Model->getData($this->session->userdata('loginid'),$this->session->userdata('isMobile')?'mobile':'default','watch');
$request.='ECHO "watchl":'."\nLISTP1 #".$portfolio." -WL ".($ph?$resultArray[0]['watch']:"")."\n";
$request.='ECHO ,"watchs":'."\nLISTP1 #".$portfolio." -WS\nECHO ,\n";
}
}
$request.="RISk\nECHO ,\nPnL\n";
if ($result=$this->getData($request."BYE\n")) {
if (result!='') $out=$result."\n";
}
ob_start('ob_gzhandler');
echo "{".$out."}";
ob_end_flush();
}
I can not figure out how to get the controller method result in the server method anyone who can shed some light on this would be much appreciated .
Thankyou.
Your controller method is expecting to output the result as an echo statement, which goes to the browser, rather than to return it in a variable. This means your server function is having to try to capture the output of that controller method. That setup is much more awkward and prone to error.
Unless you also need to access your update_p method directly from a browser you should change your Controller to simply return the output which really means this controller is more of a library and should probably go in the libraries folder. You will need to change your controller code a bit so that instead of grabbing the parameters from $_GET you are getting them as arguments, which in CodeIgniter is what you should be doing anyway.
So from the end of update_p just do this instead of your echo:
return "{".$out."}";
Then in your xmlrpc server do this:
$controller = new ControllerClass();
$result = $controller->update_p($parameters['0']["portfolio"], $parameters['0']["filter"]);
Then do whatever you want with your $result.