I mean something that acts like unittest.mock.patch in Python.
Is it possible in PHP?
-- UPDATE1 --
I'm using PHPUnit and try to mock the dependencies of SUT that is LoginController.
One of dependencies is the Fatfree Base. It's used directly in LoginController's constructor as Base::instance().
So, what I need is it to substitute the original Base class globally with the my mock class object. To make SUT use mocked Base::instance(). The only way I found is to provide this mock object on constructor of LoginController. To make this I was obligated to change the orignial SUT code that doesn't consider unit test requirements.
But I don't like this, due to there are many places in SUT code where different dependencies are used. So I need to mock those in PHP "class registry" globally to force "client" code use my class definitions to create objects.
In couple of words I need to mock class definition on system-wide level.
I'm looking for several hours on the net and still can't find the solution.
class LoginControllerTest extends TestCase
{
public function setUp()
{
$MockF3Base = $this->createMock(Base::class);
$MockF3Base->method('get')->will($this->returnCallback(
function($p) {
$mock_params = [
'LANGUAGES'=>['en', 'ru'],
'COOKIE.lg'=>'en',
'user'=>new GuestModel(),
'AJAX'=>true,
'BASE'=>BASE_URL
];
if (array_key_exists($p, $mock_params))
{
return $mock_params[$p];
}
return null;
}));
$sut = $this->getMockBuilder(LoginController::class)
->setConstructorArgs([$MockF3Base])
->setMethods(['json_success'])
->getMock();
$this->_sut = $sut;
}
public function testMustLoginIf__()
{
$this->_sut->expects($this->once())->method('json_success')->with($this->stringContains(BASE_URL.'/kassa'));
$this->_sut->LoginAction();
}
private $_sut;
}
-- UPDATE2 --
Optional $f3 constructor parameter is added by me. Originally it was just $this->f3 = Base::instance();
class LoginController{
public function __construct($f3 = null){
$this->f3 = is_null($f3)? Base::instance(): $f3;
$this->section = $this->f3->get('PARAMS.section') or $this->section = 'index';
$this->name = substr(get_class($this), 0, -10);
$user = new GuestModel();
$session = $this->f3->get('COOKIE.PHPSESSID');
if($session && $user->load(['`session`=?', $session])){
}
$this->f3->set('user', $user);
$language = $this->f3->get('COOKIE.lg');
$language = array_key_exists($language, $this->f3->get('LANGUAGES')) ? $language : 'en';
$this->f3->set('LANGUAGE', $language);
if(!$user->dry() && ($user->session('language') != $language)){
$user->session('language', $language, true);
}
}
}
Related
I am trying to write unit test for my application. which as logging the information functionality.
To start with i have service called LogInfo, this how my class look like
use Zend\Log\Logger;
class LogInfo {
$logger = new Logger;
return $logger;
}
I have another class which will process data. which is below.
class Processor
{
public $log;
public function processData($file)
{
$this->log = $this->getLoggerObj('data');
$this->log->info("Received File");
}
public function getLoggerObj($logType)
{
return $this->getServiceLocator()->get('Processor\Service\LogInfo')->logger($logType);
}
}
here i am calling service Loginfo and using it and writing information in a file.
now i need to write phpunit for class Processor
below is my unit test cases
class ProcessorTest{
public function setUp() {
$mockLog = $this->getMockBuilder('FileProcessor\Service\LogInfo', array('logger'))->disableOriginalConstructor()->getMock();
$mockLogger = $this->getMockBuilder('Zend\Log\Logger', array('info'))->disableOriginalConstructor()->getMock();
$serviceManager = new ServiceManager();
$serviceManager->setService('FileProcessor\Service\LogInfo', $mockLog);
$serviceManager->setService('Zend\Log\Logger', $mockLogger);
$this->fileProcessor = new Processor();
$this->fileProcessor->setServiceLocator($serviceManager);
}
public function testProcess() {
$data = 'I have data here';
$this->fileProcessor->processData($data);
}
}
I try to run it, i am getting an error "......PHP Fatal error: Call to a member function info() on a non-object in"
i am not sure , how can i mock Zend logger and pass it to class.
Lets check out some of your code first, starting with the actual test class ProcessorTest. This class constructs a new ServiceManager(). This means you are going to have to do this in every test class, which is not efficient (DRY). I would suggest constructing the ServiceMananger like the Zend Framework 2 documentation describes in the headline Bootstrapping your tests. The following code is the method we are interested in.
public static function getServiceManager()
{
return static::$serviceManager;
}
Using this approach makes it possible to obtain the instance of ServiceManager through Bootstrap::getServiceManager(). Lets refactor the test class using this method.
class ProcessorTest
{
protected $serviceManager;
protected $fileProcessor;
public function setUp()
{
$this->serviceManager = Bootstrap::getServiceManager();
$this->serviceManager->setAllowOverride(true);
$fileProcessor = new Processor();
$fileProcessor->setServiceLocator($this->serviceManager);
$this->fileProcessor = $fileProcessor;
}
public function testProcess()
{
$mockLog = $this->getMockBuilder('FileProcessor\Service\LogInfo', array('logger'))
->disableOriginalConstructor()
->getMock();
$mockLogger = $this->getMockBuilder('Zend\Log\Logger', array('info'))
->disableOriginalConstructor()
->getMock();
$serviceManager->setService('FileProcessor\Service\LogInfo', $mockLog);
$serviceManager->setService('Zend\Log\Logger', $mockLogger);
$data = 'I have data here';
$this->fileProcessor->processData($data);
}
}
This method also makes it possible to change expectations on the mock objects per test function. The Processor instance is constructed in ProcessorTest::setUp() which should be possible in this case.
Any way this does not solve your problem yet. I can see Processor::getLoggerObj() asks the ServiceManager for the service 'Processor\Service\LogInfo' but your test class does not set this instance anywhere. Make sure you set this service in your test class like the following example.
$this->serviceManager->setService('Processor\Service\LogInfo', $processor);
I'm having some problems understanding mock objects.
What I want is an observer that works normally but do make sure the methods are called with the right parameters.
from what I understand so far this should be what I'm looking for:
Observer:
class Observer
{
public function returnFooIfBar($bar)
{
return ($bar == 'bar') ? 'foo' : ;
}
}
Subject:
class Subject
{
$obs;
__construct(Observer $dependency)
{
$this->obs = $dependency;
}
public function tradeStrings($string)
{
$this->obs->returnFooIfBar($string);
}
}
Test:
class SubjectTest
{
public function testCallsObsMethod()
{
$obs = $this->getMock('Observer') ;
$obs->expect($this->once())
->method('returnFooIfBar')
->with($this->equlTo('bar')) ;
$subj = new Subject($obs);
$returnString= $subj->TradeStrings('bar') ;
$this->assertEqual('foo', $returnString) ;
}
}
From what I understand this tests that:
Observer::getFooIfBar is called once.
Observer::getFooIfBar got the string 'bar'
3.The method works as defined in the class and return 'foo' as a string.
As I understand no functionality of the original class is changed, other than the constructor / autoloading not being run.
If I mock a method when running getMock() the mocked object's method will only return something if I specify it.
$obs = $this->getMock('Observer', array('returnFooIfBar'));
$obs->expects($this->once())
->method('returnFooIfBar')
->with('bar')
->will($this->returnValue('foo');
Do I understand this right? if not could you please clarify for me as I would love some clarity on this. :)
Edit: Changed the post to make it clearer what I am after and how I currently understand it.
If you let phpunit create a mocked object, it internally builds up a new temporary class which extends the original one and implements all methods of this class with mock specific code.
The idea behind this is to decouple objects in your test cases. while your given example is valid, you would not use it this way.
but your example test would fail anyway, because the mocked function returnStringFooIfBar would not return anything.
this is how it should work:
$obs = $this->getMock('observer') ;
$obs->expect($this->once())
->method('returnStringFooIfBar')
->with($this->equlTo('bar'))
->will($this->returnValue('foo'));
$returnString= $obs->returnStringFooIfBar ('bar') ;
$this->assertEqual('foo', $returnString) ;
but a real world test case would involve some object to test:
class TestObject {
private $observer;
public function __construct($observer) {
$this->observer = $observer;
}
public function doMagicAndNotify() {
// do heavy magic
//notify observer
$obsresult = $this->observer->returnStringFooIfBar('bar');
return 'didmagic';
}
}
class TestObjectTest extends PHPUnit_Framework_TestCase {
public function testObserverCalling() {
$obs = $this->getMock('observer') ;
$obs->expect($this->once())
->method('returnStringFooIfBar')
->with($this->equlTo('bar'));
$test = new TestObject($obs);
$returnString= $test->doMagicAndNotify() ;
$this->assertEqual('didmagic', $returnString) ;
}
}
EDIT:
What I want is an observer that works normally but do make sure the methods are called with the right parameters.
As I understand no functionality of the original class is changed,
other than the constructor / autoloading not being run.
It is actually the other way around. The temporary child class of Observer overwrites all (or specified) methods and changes the original functionality (the parent of mocked methods is simply not executed). it does not overwrite the constructor, its called anyway.
It is not possible to assert a method call of a mocked method and call its original method at the same sime.
See Method Template and the Generator for reference.
And please remember: you are not testing the correct behaviour of your observer here, your are mocking its beheviour in order to test the subject.
sitenote: $this->returnCallback($someCallback) is a mighty function and might help you.
i don't like the idea, but you could do something like this:
public function testObserverCalling() {
$obs = new Observer();
$obsmock = $this->getMock('observer') ;
$obsmock->expect($this->once())
->method('returnStringFooIfBar')
->with($this->equlTo('bar'))
->will($this->returnCallback(array($obs, 'returnStringFooIfBar')));
$test = new TestObject($obsmock);
$returnString= $test->doMagicAndNotify() ;
$this->assertEqual('didmagic', $returnString) ;
}
You can do this now in phpunit with enableProxyingToOriginalMethods().
class SubjectTest extends \PHPUnit\Framework\TestCase
{
public function testCallsObsMethod()
{
$obs = $this->getMockBuilder('Observer')
->enableProxyingToOriginalMethods()
->getMock();
$obs->expect($this->once())
->method('returnFooIfBar');
$subj = new Subject($obs);
$returnString= $subj->tradeStrings('bar') ;
$this->assertEqual('foo', $returnString) ;
}
}
I'm used to the habit of writing like this:
$results = SomeModelQuery::create()->filterByFoo('bar')->find();
However this does not scale for unit testing because I can't inject a mock object, i.e. I can't affect what data is returned. I'd like to use fixture data, but I can't.
Nor does it seem great to inject an object:
class Foo
{
public __construct($someModelQuery)
{
$this->someModelQuery = $someMOdelQuery;
}
public function doSthing()
{
$results = $this->someModelQuery->filterByFoo('bar')->find();
}
}
DI feels horrible. I have tens of query objects to mock and throw. Setting through constructor is ugly and painful. Setting using method is wrong because it can be forgotten when calling. And it feels painful to always for every single lib and action to create these query objects manually.
How would I elegantly do DI with PropelORM query classes? I don't want to call a method like:
$oneQuery = OneQuery::create();
$anotherQuery = AnotherQuery::create();
// ... 10 more ...
$foo = new Foo($oneQuery, $anotherQuery, ...);
$foo->callSomeFunctionThatNeedsThose();
In my opinion (and Martin Folowers's) there is a step between calling everything statically and using Dependency Injection and it may be what you are looking for.
Where I can't do full DI (Zend Framework MVC for example) I will use a Service Locator. A Service Layer will be the place that all your classes go to get there dependencies from. Think of it as a one layer deep abstraction for your classes dependencies. There are many benefits to using a Service Locator but I will focus on testability in this case.
Let's get into some code, here is are model query class
class SomeModelQuery
{
public function __call($method, $params)
{
if ($method == 'find') {
return 'Real Data';
}
return $this;
}
}
All it does is return itself unless the method 'find' is called. Then is will return the hard-coded string "Real Data".
Now our service locator:
class ServiceLocator
{
protected static $instance;
protected $someModelQuery;
public static function resetInstance()
{
static::$instance = null;
}
public static function instance()
{
if (self::$instance === null) {
static::$instance = new static();
}
return static::$instance;
}
public function getSomeModelQuery()
{
if ($this->someModelQuery === null) {
$this->someModelQuery = new SomeModelQuery();
}
return $this->someModelQuery;
}
public function setSomeModelQuery($someModelQuery)
{
$this->someModelQuery = $someModelQuery;
}
}
This does two things. Provides a global scope method instance so you can always get at it. Along with allowing it to be reset. Then providing get and set methods for the model query object. With lazy loading if it has not already been set.
Now the code that does the real work:
class Foo
{
public function doSomething()
{
return ServiceLocator::instance()
->getSomeModelQuery()->filterByFoo('bar')->find();
}
}
Foo calls the service locator, it then gets an instance of the query object from it and does the call it needs to on that query object.
So now we need to write some unit tests for all of this. Here it is:
class FooTest extends PHPUnit_Framework_TestCase
{
protected function setUp()
{
ServiceLocator::resetInstance();
}
public function testNoMocking()
{
$foo = new Foo();
$this->assertEquals('Real Data', $foo->doSomething());
}
public function testWithMock()
{
// Create our mock with a random value
$rand = mt_rand();
$mock = $this->getMock('SomeModelQuery');
$mock->expects($this->any())
->method('__call')
->will($this->onConsecutiveCalls($mock, $rand));
// Place the mock in the service locator
ServiceLocator::instance()->setSomeModelQuery($mock);
// Do we get our random value back?
$foo = new Foo();
$this->assertEquals($rand, $foo->doSomething());
}
}
I've given an example where the real query code is called and where the query code is mocked.
So this gives you the ability to inject mocks with out needing to inject every dependency into the classes you want to unit test.
There are many ways to write the above code. Use it as a proof of concept and adapt it to your need.
I am looking to incorporate a testing framework into a project I am building and came across Enhance PHP which I like but I am having some difficulty finding relevant information on-line since "enhance php" is such a commonly used phrase.
Has anyone worked with this framework that might be able to point me toward some helpful guide? Have you worked with a unit test framework that you think is amazingly better?
Thanks in advance.
In response to Gotzofter, this is the class to be tested:
<?php
include_once('EnhanceTestFramework.php');
class ExampleClass
{
private $OtherClass;
function __construct($mock = null)
{
if ($mock == null)
$this->OtherClass = new OtherExampleClass();
else
$this->OtherClass = $mock;
}
public function doSomething()
{
return $this->OtherClass->getSomething(1, 'Arg2');
}
}
class OtherExampleClass
{
public function getSomething()
{
return "Something";
}
}
class ExampleClassTests extends \Enhance\TestFixture
{
public function setUp()
{
}
public function tearDown()
{
}
public function verifyWithAMock()
{
$mock = \Enhance\MockFactory::createMock('OtherExampleClass');
$mock->addExpectation(
\Enhance\Expect::method('getSomething')
->with(1, 'Arg2')
->returns('Something')
->times(1)
);
$target = new ExampleClass($mock);
$result = $target->doSomething();
\Enhance\Assert::areIdentical("Something", $result);
$mock->verifyExpectations();
}
}
\Enhance\Core::runTests();
look at my constructor for ExampleClass.
Because enhance-php's site example injects the $mock object by calling new ExampleClass($mock), I am forced to change my ExampleClass constructor to handle a $mock as an input parameter.
Do I have to handle this for all classes that I want to subject to unit testing with the framework?
Thanks.
This:
function __construct()
{
$this->OtherClass = new OtherExampleClass;
}
Should be:
function __construct($otherClass)
{
$this->OtherClass = $otherClass;
}
Your mock is never injected at this point in your test:
$target = new ExampleClass($mock);
One thing I would recommend no matter what testing framework you are using is type-hinting against the expected class, or interface.
<?php
class ExampleClass
{
private $OtherClass; // OtherClass instance
public function __construct(OtherClass $OtherClass=null)
{
// ...
}
}
I'm no di expert, but I don't see the problem in letting each class call new if an instance isn't provided for a particular dependency. You could also of course take the approach where you use setter methods to configure dependencies.
<?php
class class ExampleClass
{
private $OtherClass; // OtherClass instance
public function setOtherClass(OtherClass $OtherClass)
{
$this->OtherClass = $OtherClass;
}
}
It is lame that the ExampleClass in the sample code doesn't even define the doSomething method from the ExampleDependencyClassTests, but if I understand correctly it looks like Enhance PHP is not forcing you to take a particular style of dependency injection. You can write the test class however you want, so for example if you took the setter method approach I mentioned above, you could change the example mock code to
<?php
class ExampleDependencyClassTests extends \Enhance\TestFixture
{
public function verifyWithAMock()
{
$mock = \Enhance\MockFactory::createMock('ExampleDependencyClass');
$mock->addExpectation(
\Enhance\Expect::method('getSomething')
->with(1, 'Arg2')
->returns('Something')
->times(1)
);
$target = new ExampleClass();
$target->setExampleDependencyClass($mock);
$result = $target->doSomething();
$mock->verifyExpectations();
}
}
Of course it would probly make sense to make the appropriate revisions to the ExampleClass!
<?php
class ExampleClass
{
private $ExampleDependencyClass;
public function addTwoNumbers($a, $b)
{
return $a + $b;
}
public function setExampleDependencyClass(
ExampleDependencyClass $ExampleDependecyClass
) {
$this->ExampleDependecyClass = $ExampleDependecyClass;
}
public function doSomething($someArg)
{
return 'Something';
}
}
I've worked with PHPUnit quite a bit, and honestly you'll have to face the same challenges with Mocks there. My 2 cents, try to model your tests without Mocks if possible ;)
There is a tutorial on NetTuts titled Testing Your PHP Codebase With Enhance PHP, which will definitely help you to get started.
And there is a Quick Start Guide on Enhance PHP.
This is a follow-on from a previous question I had: How to decouple my data layer better and restrict the scope of my unit tests?
I've read around on Zend and DI/IoC and came up with the following changes to my code:
Module Bootstrap
class Api_Bootstrap extends Zend_Application_Module_Bootstrap
{
protected function _initAllowedMethods()
{
$front = Zend_Controller_Front::getInstance();
$front->setParam('api_allowedMethods', array('POST'));
}
protected function _initResourceLoader()
{
$resourceLoader = $this->getResourceLoader();
$resourceLoader->addResourceType('actionhelper', 'controllers/helpers', 'Controller_Action_Helper');
}
protected function _initActionHelpers()
{
Zend_Controller_Action_HelperBroker::addHelper(new Api_Controller_Action_Helper_Model());
}
}
Action Helper
class Api_Controller_Action_Helper_Model extends Zend_Controller_Action_Helper_Abstract
{
public function preDispatch()
{
if ($this->_actionController->getRequest()->getModuleName() != 'api') {
return;
}
$this->_actionController->addMapper('account', new Application_Model_Mapper_Account());
$this->_actionController->addMapper('product', new Application_Model_Mapper_Product());
$this->_actionController->addMapper('subscription', new Application_Model_Mapper_Subscription());
}
}
Controller
class Api_AuthController extends AMH_Controller
{
protected $_mappers = array();
public function addMapper($name, $mapper)
{
$this->_mappers[$name] = $mapper;
}
public function validateUserAction()
{
// stuff
$accounts = $this->_mappers['account']->find(array('username' => $username, 'password' => $password));
// stuff
}
}
So, now, the controller doesn't care what specific classes the mappers are - so long as there is a mapper...
But how do I now replace those classes with mocks for unit-testing without making the application/controller aware that it is being tested? All I can think of is putting something in the action helper to detect the current application enviroment and load the mocks directly:
class Api_Controller_Action_Helper_Model extends Zend_Controller_Action_Helper_Abstract
{
public function preDispatch()
{
if ($this->_actionController->getRequest()->getModuleName() != 'api') {
return;
}
if (APPLICATION_ENV != 'testing') {
$this->_actionController->addMapper('account', new Application_Model_Mapper_Account());
$this->_actionController->addMapper('product', new Application_Model_Mapper_Product());
$this->_actionController->addMapper('subscription', new Application_Model_Mapper_Subscription());
} else {
$this->_actionController->addMapper('account', new Application_Model_Mapper_AccountMock());
$this->_actionController->addMapper('product', new Application_Model_Mapper_ProductMock());
$this->_actionController->addMapper('subscription', new Application_Model_Mapper_SubscriptionMock());
}
}
}
This just seems wrong...
It is wrong, your system under test shouldn't have any knowledge of mock objects at all.
Thankfully, because you have DI in place, it doesn't have to. Just instantiate your object in the test, and use addMapper() to replace the default mappers with mocked versions.
Your test case should look something like:
public function testBlah()
{
$helper_model = new Api_Controller_Action_Helper_Model;
$helper_model->_actionController->addMapper('account', new Application_Model_Mapper_AccountMock());
$helper_model->_actionController->addMapper('product', new Application_Model_Mapper_ProductMock());
$helper_model->_actionController->addMapper('subscription', new Application_Model_Mapper_SubscriptionMock());
// test code...
}
You could also put this code in your setUp() method so that you don't have to repeat it for every test.
So, after a few misses, I settled on rewriting the action helper:
class Api_Controller_Action_Helper_Model extends Zend_Controller_Action_Helper_Abstract
{
public function preDispatch()
{
if ($this->_actionController->getRequest()->getModuleName() != 'api') {
return;
}
$registry = Zend_Registry::getInstance();
$mappers = array();
if ($registry->offsetExists('mappers')) {
$mappers = $registry->get('mappers');
}
$this->_actionController->addMapper('account', (isset($mappers['account']) ? $mappers['account'] : new Application_Model_Mapper_Account()));
$this->_actionController->addMapper('product', (isset($mappers['product']) ? $mappers['product'] : new Application_Model_Mapper_Product()));
$this->_actionController->addMapper('subscription', (isset($mappers['subscription']) ? $mappers['subscription'] : new Application_Model_Mapper_Subscription()));
}
}
This means that I can inject any class I like via the registry, but have a default/fallback to the actual mapper.
My test case is:
public function testPostValidateAccount($message)
{
$request = $this->getRequest();
$request->setMethod('POST');
$request->setRawBody(file_get_contents($message));
$account = $this->getMock('Application_Model_Account');
$accountMapper = $this->getMock('Application_Model_Mapper_Account');
$accountMapper->expects($this->any())
->method('find')
->with($this->equalTo(array('username' => 'sjones', 'password' => 'test')))
->will($this->returnValue($accountMapper));
$accountMapper->expects($this->any())
->method('count')
->will($this->returnValue(1));
$accountMapper->expects($this->any())
->method('offsetGet')
->with($this->equalTo(0))
->will($this->returnValue($account));
Zend_Registry::set('mappers', array(
'account' => $accountMapper,
));
$this->dispatch('/api/auth/validate-user');
$this->assertModule('api');
$this->assertController('auth');
$this->assertAction('validate-user');
$this->assertResponseCode(200);
$expectedResponse = file_get_contents(dirname(__FILE__) . '/_testPostValidateAccount/response.xml');
$this->assertEquals($expectedResponse, $this->getResponse()->outputBody());
}
And I make sure that I clear the default Zend_Registry instance in my tearDown()
Below is my solution to inject a mocked timestamp for a ControllerTest unit test, which is similar to the question originally posted above.
In the ControllerTest class, a $mockDateTime is instantiated and added as a parameter to the FrontController before calling dispatch().
public function testControllerAction() {
....
$mockDateTime = new DateTime('2011-01-01T12:34:56+10:30');
$this->getFrontController()->setParam('datetime', $mockDateTime);
$this->dispatch('/module/controller/action');
...
}
In the Controller class, dispatch() will pass any parameters into _setInvokeArgs(), which we extend here:
protected function _setInvokeArgs(array $args = array())
{
$this->_datetime = isset($args['datetime']) ? $args['datetime'] : new DateTime();
return parent::_setInvokeArgs($args);
}
The major advantage of this solution is that it allows dependency injection while it does not require the unit tests to clean up global state.