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));
}
}
Related
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.
I am trying to learn how to create unit tests for my custom "framework" and here is a method that verifies the email address when user is registering.
private function verifyEmail()
{
if(empty($this->email) || empty($this->email_repeat)) {
throw new \Exception('please enter email');
}
if($this->email != $this->email_repeat) {
throw new \Exception('emails don\'t match');
}
if(!filter_var($this->email, FILTER_VALIDATE_EMAIL)) {
throw new \Exception('E-mail is invalid');
}
$isTaken = $this->db->getRow("SELECT COUNT(*) as count FROM users WHERE email = ?", [$this->email]);
if($isTaken->count > 0){
throw new \Exception('E-mail is taken');
}
}
And here is the unit test
class RegisterTest extends \PHPUnit\Framework\TestCase
{
public function testVerifyEmail() {
// what do i type here?
}
}
So, what do I type in the testVerifyEmail() method to pass an email to be tested? I am going through the documentation but as a newbie the information is overwhelming and I can't find a solution.
You can use PhpUnit DataProvider to provide parameters for your test methods.
https://phpunit.de/manual/6.5/en/writing-tests-for-phpunit.html#writing-tests-for-phpunit.data-providers
The example here will execute testMethod 4 times (one for each $data item).
<?php
use PHPUnit\Framework\TestCase;
class DataTest extends TestCase
{
/**
* #dataProvider myProvider
*/
public function testMethod($a, $b, $expected)
{
var_dump($a,$b,$expected);
//... your assertions here
}
public function myProvider()
{
$data = [
//each item represents the related method parameter
//the first time $a = 'valueOfA-0', $b='valueOfB-0',$expected='valueOfExpected-0'
//and so on, for each array
['valueOfA-0', 'valueOfB-0', 'valueOfExpected-0'],
['valueOfA-1', 'valueOfB-1', 'valueOfExpected-1'],
['valueOfA-2', 'valueOfB-2', 'valueOfExpected-2'],
['valueOfA-3', 'valueOfB-3', 'valueOfExpected-3'],
];
return $data;
}
}
//values of testMethod parameters each time
//$a = 'valueOfA-0', $b='valueOfB-0', $expected='valueOfExpected-0'
//$a = 'valueOfA-1', $b='valueOfB-1', $expected='valueOfExpected-1'
//$a = 'valueOfA-2', $b='valueOfB-2', $expected='valueOfExpected-2'
//$a = 'valueOfA-3', $b='valueOfB-3', $expected='valueOfExpected-3'
I want to use a private method in a class called by public methods from the same class.
class Foo
{
private $updateAll = true;
private $updateA = false;
private $updateB = false;
public function updateA()
{
$this->updateAll = false;
$this->updateA = true;
$this->updateB = false;
$this->update();
}
public function updateB()
{
$this->updateAll = false;
$this->updateA = false;
$this->updateB = true;
$this->update();
}
private function update()
{
// Code here...
if ($this->updateAll) {
// set all api call params
}
if ($this->updateA) {
// set api call param
}
if ($this->updateB) {
// set api call param
}
// Code here...
}
}
Is this a proper use of class properties as arguments?
It works but I don't know whether there is a better way to do this. My purpose is to kinda use dynamic method arguments without the need to pass 3 arguments to update().
Your code is not wrong and should work just fine as you say, but it does feel a bit weird... I think any of the following approaches is cleaner and more flexible than your code. I'm sure there will be lots of other options perfectly valid, these are just some ideas...
Multiple method arguments
As it's been suggested to you in the comments, I think the normal way to do that is actually just adding the arguments to the update() method...
class Updater
{
public function update($all = true, $a = false, $b = false)
{
// Code...
}
}
One constant method argument
However, in your example, the options seem to be mutually exclusive (any combination of 2 options is redundant), so you can do perfectly fine with just one parameter!
class Updater
{
const UPDATE_ALL = 'all';
const UPDATE_A = 'a';
const UPDATE_B = 'b';
public function update($updateMode = self::UPDATE_ALL)
{
// Code...
}
}
Command pattern
If your example is not realistic, and you have a scenario with lots of options that are not mutually exclusive, I'd use something similar to a command pattern, where the class in charge to define the options of the operations is different from the class that performs the operation...
class Updater
{
public function update(UpdateCommand $command)
{
// Code...
}
}
class UpdateCommand
{
public $paramA = false;
public $paramB = false;
// ...
public $paramZ = false;
}
Fluent interface
Or you could also use a fluent interface. Although that's a bit harder to test...
class Updater
{
private $paramA = false;
private $paramB = false;
// ...
private $paramZ = false;
public function withA()
{
$this->paramA = true;
return $this;
}
public function withB()
{
$this->paramB = true;
return $this;
}
// ...
public function withZ()
{
$this->paramZ = true;
return $this;
}
public function run()
{
// Code...
}
}
I basically need to call one of two constructors from my PHP class dependent on wheter or not verification is needed.
My current code looks like this:
class Event extends Generic {
private $user_id;
function __construct($user_id=null) {
$this->user_id = $user_id;
}
public function verify($user_id=null, $identifier=null) {
if (parent::verifyUser($user_id, $identifier)) {
return new Event($user_id);
}
else {
// what gets retruend here in the event of a failure???
}
}
public function noverify($user_id=null) {
return new Event(user_id);
}
}
Calling the code like so:
$event = new Event();
$obj1 = $event->noverify(5);
$obj2 = $event->verify(5, 100);
I'm really not clear how I should handle the event of a failed constructor if the condition inside fails. Am I heading down the wrong path here?
I would either throw an Exception or return FALSE:
else {
throw new Exception('verification failed');
}
or
else {
return FALSE;
}
My Control Class basically picks which object / class to instantiate. Because this is basically what it does it naturally has many objects / classes it calls.
If I use dependency injection I will be injecting all of these objects. This seems bad for two reasons.
I've heard that about 3 dependent objects / classes is normal to KISS ( Keep it Simple Smarty)
Only one of the objects / classes will be used. So in a sense the others are instantiated for no reason.
How do I resolve these design considerations to satisfy - decoupled code, but simple and used code?
What you do is that you actually map some parameter onto some functionality, a so called script or action.
So in the end you only need a convention how to map that parameter or name onto some function. As functions can be somewhere (in some other object, in the global space, anonymous), you don't really need to inject many objects into your control class, but functions and the mapping.
If you would than even add some more differentiation with function name and parameters (or "modules" and "actions"), well then, you could drastically reduce your code and you can actually make the request inject the dependency:
Script or Action
Mapping: Actions:
"*" "_.Exception.Invalid ajax_type"
"signin_control" "A.SignIn.invoke"
"signup_control" "A.SignUp.invoke"
"tweet_control" "A.Tweet.add"
"ControlBookmark_add" "A.Bookmark.add"
"ControlBookmark_delete" "A.Bookmark.delete"
"ControlTryIt" "B.ControlTryIt"
"ControlSignOut" "C.SignOut"
Implementation:
$action = $map[isset($map[$ajax_type]) ? $ajax_type : '*'];
Session::start();
call_user_func_array(
'call_user_func_array',
explode('.', $action) + array(NULL, NULL, NULL)
);
function _($a, $b) {
throw new $a($b);
}
function A($a, $b) {
$maker = new ObjectMaker();
$maker->$a()->$b();
}
function B($a) {
new $a();
}
function C($a) {
Session::finish();
B($a);
}
This pseudo-code shows the actual business of your control class: Call some functions based on it's input. The concrete dependencies are:
ObjectMaker
Session
$map
As session is static, you should replace it with something that actually can be injected.
As $map is an array, it can be injected, but the logic of the mapping might need to become something more internal, so if $map is an ArrayAccess, this can happen already.
The non-concrete dependencies are hidden inside the actual $ajax_type, so these dependencies are dependent on that parameter through mapping, which is already a dependency. So the last dependency is:
$ajax_type
This dependency is related to both the control class and the mapping. So the control class itself could be made a dependency to the ajax type as well. But as you use a static global function, I'll simplify this inside a class function so actually dependencies can be passed into it. I put the factory into a global function and the ajax types are loaded from an ini-file:
function ajax_control_factory($inifile)
{
$maker = new ObjectMaker();
$session = new SessionWrap();
$types = new AjaxTypesIni($inifile);
return new AjaxControl($maker, $session, $types);
}
$control = ajax_control_factory($inifile);
printf("Call an nonexistent ajax type: ");
try {
$control->invokeByType('blurb');
printf(" - I so failed!\n");
} catch (Exception $e) {
printf("Exception caught! All good!\n");
}
printf("Add me a bookmark: ");
$control->invokeByType("ControlBookmark_add");
printf("Done! Fine! Superb this works!\n");
printf("Do the two control functions: ");
$control->invokeByType("ControlTryIt");
$control->invokeByType("ControlSignOut");
printf("Done! Fine! Superb this works!\n");
Ini file:
* = _.Exception.Invalid ajax_type
signin_control = A.SignIn.invoke
signup_control = A.SignUp.invoke
tweet_control = A.Tweet.add
ControlBookmark_add = A.Bookmark.add
ControlBookmark_delete = A.Bookmark.delete
ControlTryIt = B.ControlTryIt
ControlSignOut = C.SignOut
To have this work, this needs some stubs for mocking, which is easy with your example:
class Mock
{
public $stub;
public function __call($name, $args)
{
return class_exists($this->stub) ? new $this->stub() : $this->stub;
}
}
class ObjectMaker extends Mock
{
public $stub = 'Mock';
}
class ControlTryIt {}
class SignOut {}
class SessionWrap
{
public function start()
{
// session::start();
}
public function stop()
{
// session::finish();
}
}
Those are enough to run the code above which will give:
Call an nonexistent ajax type: Exception caught! All good!
Add me a bookmark: Done! Fine! Superb this works!
Do the two control functions: Done! Fine! Superb this works!
The ajax types:
class AjaxTypes extends ArrayObject
{
private $default;
private $types;
public function __construct(array $types, $default)
{
parent::__construct($types);
$this->default = $default;
}
public function offsetGet($index)
{
return parent::offsetExists($index) ? parent::offsetGet($index) : $this->default;
}
}
class AjaxTypesIni extends AjaxTypes
{
public function __construct($inifile)
{
$map = parse_ini_file($inifile);
if (!isset($map['*'])) throw new UnexpectedValueException('No * entry found.');
$default = $map['*'];
unset($map['*']);
parent::__construct($map, $default);
}
}
And the ajax controler:
class AjaxControl
{
private $types;
private $maker;
private $session;
public function __construct(ObjectMaker $maker, SessionWrap $session, AjaxTypes $types)
{
$this->types = $types;
$this->maker = $maker;
$this->session = $session;
}
public function invokeByType($type)
{
$session = $this->session;
$maker = $this->maker;
$invoke = function($action) use ($session, $maker)
{
$_ = function($a, $b)
{
throw new $a($b);
};
$A = function($a, $b) use ($maker)
{
$maker->$a()->$b();
};
$B = function ($a)
{
new $a();
};
$C = function ($a) use ($B, $session)
{
$session->stop();
$B($a);
};
$args = explode('.', $action) + array(NULL, NULL, NULL);
$func = array_shift($args);
call_user_func_array(${$func}, $args);
};
$invoke($this->types[$type]);
$this->session->start();
}
}
This is just exemplary. There is no guarantee that this fits as a design for your needs, just for demonstrating purposes. What it shows is that your actual controller function is not normalized / modular enough. When you better analyze the dependencies that exist and you inject them instead that you hardencode them, you will automatically find the best way to design your system.
What this example shows as well is that you have a tons of hidden dependencies for the request and response. You really need to draw lines somewhere and define what you pass through and in which direction. Say goodbye to global static state. Always inject. You can even start with function that need everything as parameters if it helps.
Solved:
By placing the dependency injection in the factory pattern ( Object Maker ), I can pull out all of the dependencies into one dependency - Object Maker - note below.
PHP Control
class Control
{
public static function ajax($ajax_type)
{
Session::start();
switch($ajax_type)
{
case 'signin_control': // uses Message, Text, Database
$Object = new ObjectMaker();
$ObjectSignIn=$Object->makeSignIn();
$ObjectSignIn->invoke();
break;
case 'signup_control':// uses Message, Text, Database
$Object = new ObjectMaker();
$ObjectSignUp=$Object->makeSignUp();
$ObjectSignUp->invoke();
break;
case 'tweet_control':// uses Message, Text, Database
$Object = new ObjectMaker();
$ObjectTweet=$Object->makeTweet();
$ObjectTweet->add();
break;
case 'ControlBookmark_add': // uses Message, Text, Database
$Object = new ObjectMaker();
$ObjectBookmark = $Object->makeBookmark();
$ObjectBookmark->add();
break;
case 'ControlBookmark_delete':// uses Database
$Object = new ObjectMaker();
$ObjectBookmark=$Object->makeBookmark();
$ObjectBookmark->delete();
break;
case 'ControlTryIt': // Why Not Session
new ControlTryIt();
break;
case 'ControlSignOut':
Session::finish();
new ControlSignOut();
break;
default:
throw new Exception('Invalid ajax_type');
}
}
ObjecMaker
class ObjectMaker
{
public function makeSignUp()
{
$DatabaseObject = new Database();
$TextObject = new Text();
$MessageObject = new Message();
$SignUpObject = new ControlSignUp();
$SignUpObject->setObjects($DatabaseObject, $TextObject, $MessageObject);
return $SignUpObject;
}
public function makeSignIn()
{
$DatabaseObject = new Database();
$TextObject = new Text();
$MessageObject = new Message();
$SignInObject = new ControlSignIn();
$SignInObject->setObjects($DatabaseObject, $TextObject, $MessageObject);
return $SignInObject;
}
public function makeTweet( $DatabaseObject = NULL, $TextObject = NULL, $MessageObject = NULL )
{
if( $DatabaseObject == 'small' )
{
$DatabaseObject = new Database();
}
else if( $DatabaseObject == NULL )
{
$DatabaseObject = new Database();
$TextObject = new Text();
$MessageObject = new Message();
}
$TweetObject = new ControlTweet();
$TweetObject->setObjects($DatabaseObject, $TextObject, $MessageObject);
return $TweetObject;
}
public function makeBookmark( $DatabaseObject = NULL, $TextObject = NULL, $MessageObject = NULL )
{
if( $DatabaseObject == 'small' )
{
$DatabaseObject = new Database();
}
else if( $DatabaseObject == NULL )
{
$DatabaseObject = new Database();
$TextObject = new Text();
$MessageObject = new Message();
}
$BookmarkObject = new ControlBookmark();
$BookmarkObject->setObjects($DatabaseObject,$TextObject,$MessageObject);
return $BookmarkObject;
}
}