I am trying to design a Database($db) Mock with Atoum that would return different values depending on previous method calls (and arguments).
I'm using PHP 5.6 and Atoum 3.2
Here is what I tried:
$this->calling($db)->select = function($table, array $bind, $boolOperator = "AND") use ($permissionClientMapper, $db, $permissionsClientRaw){
if($table === $permissionClientMapper->getTableName()){
$this->calling($db)->fetchAll = function() use ($bind, $permissionsClientRaw){
if(array_key_exists('type_service', $bind) && array_key_exists('id_service', $bind) && $bind['type_service'] === 'mutu' && $bind['id_service'] === 4012){
return EXPECTED_RETURN_VALUE;
}
return null;
};
}
};
I would except the code to return the EXECTED_RETURN_VALUE when I call (with arguments):
1/ $db->select() -> This method is called as expected
2/ $db->fetchAll() -> This one is never called
I didn't find any example of this in the Atoum documentation.
Can someone confirm this is the correct way to mock successive method calls ?
I also tried to use a reference to the database in the closure
$this->calling($db)->select = function($table, array $bind, $boolOperator = "AND") use ($permissionClientMapper, &$db, $permissionsClientRaw){
if($table === $permissionClientMapper->getTableName()){
$this->calling($db)->fetchAll = function() use ($bind, $permissionsClientRaw){
if(array_key_exists('type_service', $bind) && array_key_exists('id_service', $bind) && $bind['type_service'] === 'mutu' && $bind['id_service'] === 4012){
return EXPECTED_RETURN_VALUE;
}
return null;
};
}
};
But this doesn't work either.
Edit: One workaround would probably be to use the atoum call order to return different values for each call, and then to test the mock to check it was called with the correct arguments.
I will give you some insight about your questions and hope give you some clue to find a way to solve it.
So to validate that a mock method is not called, you can use 'call' with 'never'
$this->mock($mock)->call('fetchAll')->never();
And to be called :
$this->mock($mock)->call('select')->once();
To deal with you mock answer, you can use several things, like this
$this->calling($db)->fetchAll[0] = null; // default answer
$this->calling($db)->fetchAll[1] = function () {....} // first call to method
If you want something like a chain : when use the mocked method select, and inside it we call fetchAll method then the answer is ... atoum doesn't offer yet this behavior. The best is to create an issue exposing your case.
When you use 'calling' you define the behavior of the mock. It's only when the method is called, that atoum will grab everything and resolve it.
So for me, if I understand correctly your question, I will write it like that :
$this->calling($db)->fetchAll = function() use ($bind){
if(array_key_exists('type_service', $bind) && array_key_exists('id_service', $bind) && $bind['type_service'] === 'mutu' && $bind['id_service'] === 4012){
return EXPECTED_RETURN_VALUE;
}
return null;
};
$this->calling($db)->select = function($table, array $bind, $boolOperator = "AND") use ($permissionClientMapper, $db){
if($table === $permissionClientMapper->getTableName()){
return $db->fetchAll();
}
};
// this is the same as your code. But It a bit more readable
$this->newTestedInstance;
$this->testedInstance->setDb($db);
$this->variable($this->testedInstance->doTheCallThatReturnNull())
->isEqualTo(null);
// do some change in the vars to be in the value
$this->variable($this->testedInstance->doTheCallThatReturnValue())
->isEqualTo(EXPECTED_RETURN_VALUE);
ps : to help you going further you can read http://docs.atoum.org/en/latest/asserters.html#mock and http://docs.atoum.org/en/latest/mocking_systems.html
and you can also tag the question with 'atoum'.
Related
I'm developing a module for PS1.7.8.6 which add datas to a stock movement. In order to do that, I have decorate the PrestaShop\PrestaShop\Core\Stock\StockManager following the doc.
I don't know if the decoration was made in a good way because the stock movement in the BO still use PrestaShop\PrestaShop\Core\Stock\StockManager instead of my class CustomStockManager.
But in my module, if I call my CustomStockManager, the movement is not made. I've found that the function SymfonyContainer::getInstance(); return null so the function saveMovement() return false.
Is there a way to know why the SymfonyContainer return null ?
Thanks
Finally I have found a solution. The function SymfonyContainer::getInstance() return null because the function is called from an ajax.php file which call a function from the module.
To resolve it, first, I have put the function getKernel() from this article in the file mymodule.php.
Then, il my CustomStockManager, in the function saveMovement(), I have change this
$sfContainer = SymfonyContainer::getInstance();
if (null === $sfContainer) {
return false;
}
To
$sfContainer = SymfonyContainer::getInstance();
if (null === $sfContainer) {
$moduleObj = Module::getInstanceByName('mymodule');
$sfContainer = $moduleObj->getKernel()->getContainer();
}
And because there is no context and so no user to to the movement, at the beginning of the function I add :
if (is_null(Context::getContext()->employee)) {
$context = Context::getContext();
$context->employee = new Employee(1);
}
It works now but I think this is not the best way to do that. So I will change my ajax.php file in a controller for the module.
I need to know if there is a better way to avoid Call to a member function xxxx() on null
currently I'm coding as follows but it is cumbersome.
if($event->getForm()
&& $event->getForm()->getParent()
&& $event->getForm()->getParent()->getParent()
&& $event->getForm()->getParent()->getParent()->getData()
&& $event->getForm()->getParent()->getParent()->getData()->getComponente()
){
$componente = $event->getForm()->getParent()->getParent()->getData()->getComponente();
$formModifier($event->getForm(), $componente, $defaultComponente);
}
In PHP 7 this is actually a catchable Error (if you're using hhvm it's a regular Exception):
try {
$componente = $event->getForm()->getParent()->getParent()->getData()->getComponente();
} catch (\Error $e) {
$componente = null;
}
if ($componente !== null) {
$formModifier($event->getForm(), $componente, $defaultComponente);
}
In PHP 5 there is a workaround using intermediate variables and the and keyword instead of &&:
if (
$f = $event->getForm() and
$p = $f->getParent() and
$p2 = $p->getParent() and
$d = $p2->getData() and
$componente = $d->getComponente()
) {
$formModifier($f, $componente, $defaultComponente);
}
If you use && instead of and you'll get "undefined variable" notices and this workaround won't work.
Working examples: https://3v4l.org/0S6ps
no there is no way, but at least you can do some performance improvement
$form = $event->getForm();
if(!$form){
//do error handling
return;
}
$parent = $form->getParent();
if(!$parent){
//do error handling
return;
}
$p_parent = $parent->getParent();
if(!$p_parent){
//do error handling
return;
}
$data = $p_parent->getData();
if(!$data){
//do error handling
return;
}
$component = $data->getComponente();
...
this way you call each function only once and you can do better error handling
I think this is a great example of a bad code. By having a code like this you're breaking several rules and making your life much harder than it should be.
Your code is rigid, fragile, hard to understand and maintain etc.
Simpler is ALWAYS better.
If you can't make your $xx->getComponent() a proper object easily accessible without such ugly nested relationship, you should at least encapsulate the method into something appropriate and use that instead, so if anything changes, you don't have to go full mental and change it all over the place.
This class seems strange in it's creation, but if you are not extracting these methods dynamically using __call(), you can use method_exists() in a loop inside a function, something similar to:
function getMethodChain($class,$arr = ['getForm','getParent','getParent','getData','getComponente'])
{
# First check the object is set
if(!is_object($class))
return false;
# Loop intended method chain
foreach($arr as $method) {
# Check if the method exists in the current class or passed already
$useClass = (!isset($classPass))? $class : $classPass;
# Check if the method exists in the current class
if(is_object($useClass) && method_exists($useClass,$method)) {
# Assign this class/method to use next in the loop
$classPass = $useClass->{$method}();
}
else
return false;
}
# Just send back
return (isset($classPass))? $classPass : false;
}
The use would be something like:
# This will either be the data you expect or false
$componente = getMethodChain($event);
Hi all I need to test a piece of code that call a function of another class that I can't edit now.
I need only to test It but the problem is that this function has a values passed by reference and a value returned, so I don't know how to mock It.
This is the function of column class:
public function functionWithValuePassedByReference(&$matches = null)
{
$regex = 'my regex';
return ($matches === null) ? preg_match($regex, $this->field) : preg_match($regex, $this->field, $matches);
}
This is the point where is called and where I need to mock:
$matches = [];
if ($column->functionWithValuePassedByReference($matches)) {
if (strtolower($matches['parameters']) == 'distinct') {
//my code
}
}
So I have tried
$this->columnMock = $this->createMock(Column::class);
$this->columnMock
->method('functionWithValuePassedByReference')
->willReturn(true);
If I do this return me error that index parameters doesn't exist obviously so I have tried this:
$this->columnMock = $this->createMock(Column::class);
$this->columnMock
->method('functionWithValuePassedByReference')
->with([])
->willReturn(true);
But same error, how can I mock that function?
Thanks
You can use ->willReturnCallback() to modify the argument and also return a value. So your mock would become like this:
$this->columnMock
->method('functionWithValuePassedByReference')
->with([])
->willReturnCallback(function(&$matches) {
$matches = 'foo';
return True;
});
In order for this to work, you will need to turn off cloning the mock's arguments when you build the mock. So your mock object would be built like so
$this->columnMock = $this->getMockBuilder('Column')
->setMethods(['functionWithValuePassedByReference'])
->disableArgumentCloning()
->getMock();
This really is code smell, btw. I realize that you stated that you can't change the code that you are mocking. But for other people looking at this question, doing this is causing side effects in your code and can be a source of very frustrating to fix bugs.
Pimple is a simple dependency injection container in php used in silex framework. I was going through the source code here. In the documentation the function offsetGet returns the same instance of the class that is attached to the dependency container. the relevant code for offsetGet is :
public function offsetGet($id)
{
if (!isset($this->keys[$id])) {
throw new InvalidArgumentException(sprintf('Identifier "%s" is not defined.', $id));
}
if (
isset($this->raw[$id])
|| !is_object($this->values[$id])
|| isset($this->protected[$this->values[$id]])
|| !method_exists($this->values[$id], '__invoke')
) {
return $this->values[$id];
}
if (isset($this->factories[$this->values[$id]])) {
return $this->values[$id]($this);
}
$this->frozen[$id] = true;
$this->raw[$id] = $this->values[$id];
return $this->values[$id] = $this->values[$id]($this);
}
Here, if the object is in the factories Object Store(SplObjectStorage type), it returns a new instance of the class with id $id. then in the last return again $this->values[$id] is set to a new instance of the object and that new instance is returned.
return $this->values[$id] = $this->values[$id]($this).
This is the line I fail to understand. How is this line supposed to return the same instance for different calls of offsetGet for the same $id. Won't it return a new instance every time?
Please help me. I tried a lot but I don't get it.
I looked at the source code of pimple and found out that once the object is instantiated and kept in $this->values[$id], the next call of offsetGet will return from the second if condition.
i.e this if condition:
if (
isset($this->raw[$id])
|| !is_object($this->values[$id])
|| isset($this->protected[$this->values[$id]])
|| !method_exists($this->values[$id], '__invoke')
) {
return $this->values[$id];
}
Looking at the unit tests, i found out that the objects without the magic method __invoke can be shared . If the object has a magic method __invoke(i.e the object can be treated as a function) , a new instance is returned everytime.
So, you can see that the first, second and third condition on the above if statement return false . but the fourth condition returns true and hence the $this->values[$id] returns the same instance every time.
I have method in class:
class SomeClass{
public function someMethod($status){
echo $status;
}
}
I know what $status can be 1 or 0.
Is it possible to force method work only with that values? So for code:
$class = new SomeClass();
$class->someMethod(2);
get some error (fatal, warning, notice).
P.S. if($status != 1 && $status != 2) is not good solution for me.
U can use assert function, but if statement is better