I have a Service class and a test for that, follow below:
Class
class MyCustomService
{
public function job()
{
while($this->getResponseFromThirdPartyApi()->data) {
// do some stuff...
}
return ...
}
protected function getResponseFromThirdPartyApi()
{
// Here do some curl and return stdClass
// data attribute is populated only on first curl request
}
}
Test mocking getResponseFromThirdPartyApi method
class MyCustomServiceTest
{
public function testJobImportingData()
{
$myCustomServiceMock = $this->getMockBuilder('MyCustomService')
->setMethods(array('getResponseFromThirdPartyApi'))
->getMock();
$myCustomServiceMock->expects($this->any())
->method('getResponseFromThirdPartyApi')
->willReturn($this->getResponseWithData());
$jobResult = $myCustomServiceMock->job();
// here some assertions on $jobResult
}
protected function getResponseWithData()
{
$response = new \stdClass;
$response->data = ['foo', 'bar'];
return $response;
}
}
How can I change getResponseWithData return after first call on MyCustomService while loop?
I've tried creating a custom flag on MyCustomServiceTest and checking on getResponseWithData, but fails once that mocked object doesn't call getResponseWithData method again on MyCustomServiceTest.
Any direction?
As Nico Haase suggested above, the path is to use callback.
After some research I achieved passing mock object reference to method mocked and checking flag, this results on:
class MyCustomServiceTest
{
public function testJobImportingData()
{
$myCustomServiceMock = $this->getMockBuilder('MyCustomService')
->setMethods(array('getResponseFromThirdPartyApi'))
->getMock();
$myCustomServiceMock->expects($this->any())
->method('getResponseFromThirdPartyApi')
->will($this->returnCallback(
function () use ($myCustomServiceMock) {
return $this->getResponseWithData($myCustomServiceMock)
}
));
$jobResult = $myCustomServiceMock->job();
// here some assertions on $jobResult
}
protected function getResponseWithData(&$myCustomServiceMock)
{
$response = new \stdClass;
if (isset($myCustomServiceMock->imported)) {
$response->data = false;
return $response;
}
$myCustomServiceMock->imported = true;
$response = new \stdClass;
$response->data = ['foo', 'bar'];
return $response;
}
}
Then while loop will be called only once and we be able to test without forever loop.
Related
I have a question about getting value from running a method from a string. While I am able to handle a single method call from a string I am curious how to call a chain of methods from a string.
For example. $project is an Object.
$method1 = "name";
$project->$method1; // It shows the valid results
$method2 = "get()->first()->name";
$project->get()->first()-name; // It shows the valid results
$project->$method2; // get a null result
Please help to find a way to make the $method2 work. And what happen if I have params inside those methods?
The reason here is I have made an array of customized methods. It can be run line by line, but I am thinking of a way to turn them into a loop, so it's more efficient. Put the methods in to a file then get values by looping to them.
Array = ["getvalue1()", "getvalue2()",...."getValuen()->anotherMethod()->value"]
Thanks,
If you want nested try something like this:
private function callMethodChain($model, $methodChain)
{
return array_reduce(explode('->', $methodChain), function($model, $method) {
return $model->$method;
}, $model);
}
This will go through a chain of method calls as your described. If some of the chain (the last piece) is a property I think I once rigged up the following to handle it:
protected function callMethodChain($model, $methodChain)
{
return array_reduce(explode('->', $methodChain), function($model, $method) {
try {
return $model->$method;
} catch (Exception $e) {
return $model->$method();
}
}, $model);
}
If you want to add params try replacing $model->method with:
call_user_func_array(
array($project, 'your_method'),
$params
);
Try this approach:
$method1 = 'name';
$project->{$method1}();
Run method from a string value
Use call_user_func() and call_user_func_array().
call_user_func_array() suits good if you are passing parameters
call_user_func_array(
array($project, 'your_method'),
$params
);
Chain function
function chain_fun($chain,$object)
{
return array_reduce(explode('->', $chain), function ($obj, $method) {
if(preg_match('/[()]/',$method)){
$method=trim($method,'()');
return $obj->$method();
}
return $obj->$method;
}, $object);
}
Here is test
akshay#db-3325:/tmp$ cat test.php
<?php
class Testclass
{
private $str;
function __construct()
{
$this->str = new StdClass;
}
function addA()
{
$this->str->a='A';
return $this;
}
function addB()
{
$this->str->b='B';
return $this;
}
function get()
{
return $this->str;
}
}
function chain_fun($chain,$object)
{
return array_reduce(explode('->', $chain), function ($obj, $method) {
if(preg_match('/[()]/',$method)){
$method=trim($method,'()');
return $obj->$method();
}
return $obj->$method;
}, $object);
}
$object = new Testclass();
// Output 1
print_r(chain_fun("addA()->addB()->get()", $object));
// Output 2
echo chain_fun("addA()->addB()->get()->a", $object);
?>
Output
akshay#db-3325:/tmp$ php test.php
stdClass Object
(
[a] => A
[b] => B
)
A
Please consider the following PHP code
static public function IsLicenseValid($domain)
{
if (empty($domain)) {
throw new Exception;
}
$licenseResponse = Curl::Post(['hostname' => $domain]);
$Xml = new XML();
$xmlTree = $Xml->XMLToTree($licenseResponse);
if ($xmlTree['is_valid'] == 'true') {
return true;
}
}
return false;
}
I am writing test case using PHPUnit to check the above method. I am able to cover all cases except one case in which a domain license should return true in is_valid xml node.
The REST API is so secured that it does not accept request from IPs that are not listed in their whitelist. And if someone is making a request from a non-whitelisted IP the API returns a false value for is_valid (and this is how I am covering the case for false)
I know this can be done using a mock object but I am not really sure how to write a mock object that can cover the case where a domain name is valid. Can someone please help?
Thanks in advance
To test this class, you would mock the Curl::Post call, but since it is done inline, you need to use Dependency Injection to use the Mock.
Class:
class ValidateLicense {
private $svc;
// Constructor Injection, pass the IntegratedService object here
public function __construct($Service = NULL)
{
if(! is_null($Service) )
{
if($Service instanceof LicenseAPI)
{
$this->SetService($Service);
}
}
}
function SetService(LicenseAPI $Service)
{
$this->svc = $Service
}
function ValidLicense($domain) {
$svc = $this->svc;
$result = $svc->IsLicenseValid($domain);
return $result;
}
}
class LicenseAPI {
public function IsLicenseValid($domain)
{
if( empty($domain)) {
throw new Exception;
}
$licenseResponse = Curl::Post(['hostname' => $domain]);
$Xml = new XML();
$xmlTree = $Xml->XMLToTree($licenseResponse);
if ($xmlTree['is_valid'] == 'true') {
return true;
}
return false;
}
}
Test:
class ValidateLicenseTest extends PHPUnit_Framework_TestCase
{
// Could also use dataProvider to send different returnValues, and then check with Asserts.
public function testValidLicense()
{
// Create a mock for the LicenseAPI class,
$MockService = $this->getMock('LicenseAPI', array('IsLicenseValid'));
// Set up the expectation for the return method
$MockService->expects($this->any())
->method('IsLicenseValid')
->will($this->returnValue(true));
// Create Test Object - Pass our Mock as the service
$TestClass = new ValidateLicense($MockService);
// Or
// $TestClass = new ValidateLicense();
// $TestClass->SetServices($MockService);
// Test
$domain = "localhost"; // Could be checked with the Mock functions
$this->assertTrue($TestClass->ValidLicense($domain));
}
// Could also use dataProvider to send different returnValues, and then check with Asserts.
public function testInValidLicense()
{
// Create a mock for the LicenseAPI class,
$MockService = $this->getMock('LicenseAPI', array('IsLicenseValid'));
// Set up the expectation for the return method
$MockService->expects($this->any())
->method('IsLicenseValid')
->will($this->returnValue(false));
// Create Test Object - Pass our Mock as the service
$TestClass = new ValidateLicense($MockService);
// Or
// $TestClass = new ValidateLicense();
// $TestClass->SetServices($MockService);
// Test
$domain = "localhost"; // Could be checked with the Mock functions
$this->assertFalse($TestClass->ValidLicense($domain));
}
}
I've been handed a PHP class, and I'm not interested in fully restructuring it. (it works!)
But I'd like to add a slight modification inside a few methods.
Here is one of the many methods inside the class:
<?php
class SomeFunClass {
public function getAccountInfo()
{
$request = $this->prepareRequest('get_account_info');
$response = $this->execute($request);
return $response;
}
}
?>
The return $response is a string value.
I've come to a point that I need to return the $request string, which happens to be a json string.
The prepareRequest() method always returns a json string, which is then passed to the exec() method, which simply sends the data via cURL to a domain.
I'd like to extract the $request string (when I call the getAccountInfo() method), for later review.
Here's what I'm doing now:
<?php
$api = new SomeFunClass();
$curlresponse = $api->getAccountInfo();
?>
Obviously, the example immediately above only gives me back what the cURL response would be.
Would be nice to call a method that lets me see what the $request looks like.
I'm open to suggestions.
Just return an array with the request and the response:
<?php
class SomeFunClass {
public function getAccountInfo()
{
$request = $this->prepareRequest('get_account_info');
$response = $this->execute($request);
return array('request' => $request, 'response' => $response);
}
}
?>
You can modify those methods to store the last request into an attribute of the current class :
<?php
class SomeFunClass {
$last_request;
...
public function getAccountInfo()
{
$request = $this->prepareRequest('get_account_info');
$last_request = request;
$response = $this->execute($request);
return $response;
}
public function getLastRequest()
{
return $this -> last_request;
}
}
?>
Or, better, if prepareRequest is a method of yours, then just modify this one to store the last request.
You can do something like this:
<?php
class SomeFunClass {
public $request;
public $response;
public function getAccountInfo()
{
$this->request = $this->prepareRequest('get_account_info');
$this->response = $this->execute($this->request);
return $this->response;
}
}
?>
Now, you can do something like this:
<?php
$api = new SomeFunClass();
$curlresponse = $api->getAccountInfo();
$request = $api->request;
?>
Ideally, you can do implement your class like this to take actual advantage of OOP (so that these instance variables request and response are auto-set for all your methods):
<?php
class SomeFunClass {
public $request;
public $response;
public function getAccountInfo()
{
$this->prepareRequest('get_account_info');
return $this->execute();
}
public function anotherMethod()
{
$this->prepareRequest('another_method', 'some', 'args');
return $this->execute();
}
public function prepareRequest()
{
$args = func_get_args(); // contains your arguments
$method = array_shift($args); // contains your method name
...
...
$this->request = $return // value returned by this method
}
public function execute()
{
$request = $this->request;
...
...
$this->response = $return // value returned by this method
}
}
?>
You could also do this:
<?php
class SomeFunClass {
public function reviewRequest($request)
{
return $this->prepareRequest($request);
}
}
And then:
<?php
$api = new SomeFunClass();
$request = $api->reviewRequest('get_account_info');
I have the following code and am trying to find a solution:
<?php
class t1 {
public $response = array();
public $request = array();
public function getRequest() {
return $this->request;
}
public function getResponse() {
return $this->response;
}
}
class t2 extends t1 {
public function run($f) {
$this->response = $f($this);
return $this;
}
}
$delegate = function($c)
{
// PLACEHOLDER
// This is the only place to update test code
// It's not allowed to modify any other section of this code
};
$t = new t2();
print_r(array("request" => $t->run($delegate)->getRequest(), "response" => $t->getResponse()));
?>
I assume $delegate is a dynamic function. Anyone able to walk me through this.
I'm thinking in PLACEHOLDER is should be:
I assume $delegate is a dynamic function.
Yes. $delagate is a PHP Closure.
If you define your function as:
$delegate = function($c)
{
};
and pass it to $t->run, then $c will be the $t instance.
Test method:
public function convert(AbstractMessage $message)
{
$data = array();
// Text conversion
$text = $message->getText();
if(null !== $text) {
if(!is_string($text) && (is_object($text)
&& !method_exists($text, '__toString'))) {
throw new UnexpectedTypeException(gettype($text), 'string');
}
$data['text'] = (string) $text;
}
}
How can I mock a generic object (no matter the class) that has a __toString method?
<?php
// UnderTest.php
class UnderTest
{
public function hasTostring($obj)
{
return method_exists($obj, '__toString');
}
}
// Observer.php
class ObserverTest extends PHPUnit_Framework_TestCase
{
public function testHasTostring()
{
$tester = new UnderTest();
$with = new WithToString();
$without = new WithoutToString();
$this->assertTrue($tester->hasTostring($with));
$this->assertFalse($tester->hasTostring($without));
// this automatically calls to string
// and if the method toString doesnt exists - returns E_RECOVERABLE_ERROR
// so this line should work
$x = $with . '';
// but this shouldnt work, because the method doesnt exist
// therefore you are supposed to get an exception
$this->setExpectedException('PHPUnit_Framework_Error');
$x = $without . '';
}
}
class WithToString
{
public function __toString() { return 'hi'; }
}
class WithoutToString{}