I started working with Silex and it's just great. The problem appears when trying to proper unit test my classes. Concretely the closures :(
In the following lines I explain my problem to see if any of you knows how to solve it.
Please, do not focus on the syntax but the testing problem itself.
I have a provider like this:
<?php
namespace Foo\Provider;
use Silex\Application;
use Silex\ServiceProviderInterface;
use Foo\Bar;
class BarProvider implements ServiceProviderInterface {
public function register( Application $app ) {
$app[ 'foo_bar' ] = $app->protect( function() use ( $app ) {
return new Bar( $app );
} );
}
public function boot( Application $app ) {}
}
Then I need to get an instance of the foo_bar element:
<?php
namespace Foo;
use Silex\Application;
class Clazz {
protected $bar;
public function __construct( Application $app ) {
$this->bar = $app[ 'foo_bar' ]();
}
}
This works just fine. The thing is that I'm developing using TDD (and PHPUnit) and It's impossible for me to proper test the Clazz class.
<?php
namespace Foo\Test;
use PHPUnit_Framework_TestCase;
use Silex\Application;
use Foo\Bar;
use Foo\Clazz;
class ClazzTest extends PHPUnit_Framework_TestCase {
public function testConstruct() {
$mock_bar = $this->getMock( 'Foo\Bar' );
$mock_app = $this->getMock( 'Silex\Application' );
$mock_app
->expects( $this->once() )
->method( 'offsetGet' )
->with( 'foo_bar' )
->will( $this->returnValue( $mock_bar ) );
new Class( $mock_app );
}
}
The problem here resides in the "()" after the $app[ 'foo_bar' ] in the Clazz class.
When trying to execute the test I get a "PHP Fatal error: Function name must be a string in ..." error.
I understand I cannot unit test the class this way but I don't see the proper way to do it.
How could I test this statement (because at the end the only complex statement is $this->bar = $app 'foo_bar' ; )?
Ok, I think I managed to test properly this closure! The final test looks like this:
<?php
namespace Foo\Test;
use PHPUnit_Framework_TestCase;
use Silex\Application;
use Foo\Bar;
use Foo\Clazz;
class ClazzTest extends PHPUnit_Framework_TestCase {
public function testConstruct() {
$mock_bar = $this->getMock( 'Foo\Bar' );
$mock_app = $this->getMock( 'Silex\Application' );
$mock_app
->expects( $this->once() )
->method( 'offsetGet' )
->with( 'foo_bar' )
->will( $this->returnValue( function() use( $mock_bar ) { return $mock_bar; } ) );
new Class( $mock_app );
}
}
Instead of returning the mock, I return a closure that returns the mock. This way I don't get the error while still working with the actual mock.
Can anyone tell me if this is a correct approach?
Related
I try to write, at first glance, it would seem a trivial test for my repository's "update" method:
<?php
declare(strict_types=1);
namespace Paneric\Authorization\ORM\Repository;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\ORMException;
use Paneric\Authorization\DTO\FieldDTO;
use Paneric\Authorization\ORM\Entity\Field;
use Doctrine\ORM\EntityRepository;
use Paneric\Authorization\Interfaces\FieldRepositoryInterface;
class FieldRepository extends EntityRepository implements FieldRepositoryInterface
{
const ENTITY_CLASS = Field::class;
public function __construct(EntityManagerInterface $_em)
{
parent::__construct($_em, $_em->getClassMetadata(self::ENTITY_CLASS));
}
...
public function update(int $fieldId, FieldDTO $fieldDTO): void
{
try {
$field = $this->find($fieldId);
$field->transfer($fieldDTO);
$this->_em->flush();
} catch (ORMException $e) {
echo $e->getMessage();
}
}
...
}
with a spec method:
<?php
namespace spec\Paneric\Authorization\ORM\Repository;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\QueryBuilder;
use Paneric\Authorization\DTO\FieldDTO;
use Paneric\Authorization\ORM\Entity\Field;
use Paneric\Authorization\ORM\Repository\FieldRepository;
use Doctrine\ORM\AbstractQuery;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
class FieldRepositorySpec extends ObjectBehavior
{
public function let(EntityManagerInterface $_em, ClassMetadata $classMetadata)
{
$_em->getClassMetadata(Field::class)->willReturn($classMetadata);
$this->beConstructedWith($_em);
}
...
public function it_updates(Field $field, FieldDTO $fieldDTO, EntityManagerInterface $_em)
{
$fieldId = 1;
$field = $this->find($fieldId);
$field->transfer($fieldDTO)->shouldBeCalled();
$_em->flush()->shouldBeCalled();
$this->update($fieldId, $fieldDTO);
}
...
}
and receive the following error:
Unexpected method call on Double\EntityManagerInterface\EntityManagerInterface\P1:
- find(
null,
1,
null,
null
)
expected calls were:
- getClassMetadata(
exact("Paneric\Authorization\ORM\Entity\Field")
)
- find(
exact("Paneric\Authorization\ORM\Entity\Field"),
exact(1)
)
- flush(
)
Apparently issue is related to the call:
...
$field = $this->find($fieldId);
...
Although the second remark related to getClassMetadata, looks strange, considering the fact that my spec let method:
public function let(EntityManagerInterface $_em, ClassMetadata $classMetadata)
{
$_em->getClassMetadata(Field::class)->willReturn($classMetadata);
$this->beConstructedWith($_em);
}
does its job in case of other spec tests.
Can anyone help me to solve this issue ? Thx in advance.
In my repository's "update" metod, line:
$field = $this->find($fieldId);
has to be replaced by:
$field = $this->_em->find(Field::class, $fieldId);
so the complete spec test looks like:
public function it_updates(Field $field, FieldDTO $fieldDTO, EntityManagerInterface $_em)
{
$fieldId = 1;
$_em->find(Field::class, $fieldId)->willReturn($field);
$field->transfer($fieldDTO)->shouldBeCalled();
$_em->flush()->shouldBeCalled();
$this->update($fieldId, $fieldDTO);
}
I am learning codeception and I wonder what is the difference between stubs and fixtures.
Both help me to load well-defined data and kepp tessts simple.
But when do I use
\Codeception\Util\Stub and when do I use
\Codeception\Util\Fixtures
So a stub is what Codeception uses to mock objects. In short, mocking is creating objects that simulate the behaviour of real objects.
Here is an example:
class UpdateBalance
{
public $balanceRepository;
public function __construct(BalanceRepositoryInterface $balanceRepository)
{
$this->balanceRepository = $balanceRepository;
}
public function subtract($amount, $id)
{
$updatedAmount = $this->balanceRepository->subtract($amount, $id);
if ($updatedAmount < 0) {
throw NegativeBalanceException($updatedAmount, $id);
}
return $updatedAmount;
}
}
class UpateBalanceTest extends PHPUnit_Framework_TestCase
{
public function testSubtractThrowsNegativeBalanceException()
{
$balanceRepository = Stub::make(
'BalanceRepositoryInterface'
array(
'subtract' => Stub::atLeastOnce(function() { return -100 })
)
);
$updateBalance = new UpdateBalance($balanceRepository);
$this->expectException(NegativeBalanceException::class);
$updateBalance->subtract(100, 1);
}
}
Note that we don't have a BalanceRepsository class. We have used Codeception stubs and pretended that the BalanceRepository class exists. By pretending it exists we can test the functionality of the UpdateBalance::subtract function by checking that the NegativeBalanceException is thrown.
Fixtures on the other hand would be for sharing test data throughout all your tests. If we use the UpdateBalance::subtract() example again, we could stress test the amount field ensuring it throws the correct exception depending on the amount being passed through:
// In some bootstrap or setup function
Fixtures::add('zero-amount', 0);
Fixtures::add('negative-amount', -1);
Fixtures::add('string-negative-amount', '-1');
class UpdateBalance
{
// ...
public function subtract($amount, $id)
{
if ($amount < 0) {
throw new
}
// ...
}
}
class UpateBalanceTest extends PHPUnit_Framework_TestCase
{
// ...
public function testSubtractThrowsCantSubtractNegativeAmountException()
{
$balanceRepository = Stub::make(
'BalanceRepositoryInterface'
);
$updateBalance = new UpdateBalance($balanceRepository);
$this->expectException(CantSubtractNegativeAmountException::class);
$updateBalance->subtract(Fixture::get('negative-amount'), 1);
}
}
Now we can use our pre-defined fixtures throughout all our tests. I would like to point out that using fixtures in the above example would probably be overkill, but for more complex test data like checking hexadecimal values are valid then it would be a lot more useful.
If I create a class with PHP7 multiple times, it seems it is returning always the same class instead of returning a new one each time.
For example:
function createAClass()
{
return new class
{
public static $foo=0;
};
}
$class = createAClass();
$class::$foo = 3;
echo "class:".$class::$foo."<br>";
$anotherClass = createAClass();
echo "anotherClass:".$anotherClass::$foo."<br>";
This is the output:
class:3
anotherClass:3
I though the output should be 3 and 0. What's happening? Is this a bug, or a "feature" of PHP 7? :)
btw, I was trying to use this to test a trait with static methods with PHPUnit.
I've found the documentation and it seems it is the expected behaviour:
http://php.net/manual/en/language.oop5.anonymous.php
All objects created by the same anonymous class declaration are instances of that very class.
But I needed to create a function which returned a different class each time it was called, so I came with this solution:
function createAClass()
{
$class = null;
$stamp = random_int(PHP_INT_MIN ,PHP_INT_MAX);
echo "stamp: $stamp\n";
$classcode = <<< EOT
\$class = new class {
public static \$differentiate='$stamp';
// YOUR CODE HERE
};
EOT;
echo "Source code of class:\n$classcode\n\n";
eval($classcode);
return $class;
}
$class = createAClass();
$anotherClass = createAClass();
if (get_class($class) === get_class($anotherClass)) {
echo 'same class';
} else {
echo 'different class';
}
I am using PHP Yii Framework with MongoDB(yiimongodbsuite). I have created a Model which extends from EMongoDocument.
<?php
class MyModel extends EMongoDocument
{
public $attr1;
public $attr2;
// rules, custom validations and other functions....
public function setAttributes($values, $safeOnly=true)
{
if(!is_array($values))
return;
if($this->hasEmbeddedDocuments())
{
$attributes=array_flip($safeOnly ? $this->getSafeAttributeNames() : $this->attributeNames());
foreach($this->embeddedDocuments() as $fieldName => $className)
if(isset($values[$fieldName]) && isset($attributes[$fieldName]))
{
$this->$fieldName->setAttributes($values[$fieldName], $safeOnly);
unset($values[$fieldName]);
}
}
parent::setAttributes($values, $safeOnly);
}
}
In Controller,
$dataModel = new MyModel();
$dataModel->setAttributes($_POST['MyModel']);
if($dataModel->validate()){
$dataModel->save();
}
the above code is not setting the attribute value.
Please let me know if there is any mistake.
You need to make sure that the 'safe' validation rules is used on each level.
To understand more read this http://www.yiiframework.com/wiki/161/understanding-safe-validation-rules/
Try to determine which valdation errors you have:
if(!$model->validate()) {
die( print_r($model->getErrors()) );
}
Currently, I have a PHPUnit test case that extends PHPUnit_Extensions_SeleniumTestCase. Each function that starts requires a $this->setBrowserUrl() and defaults to starting a new Firefox browser window with each function call.
I want to have a test case that launches the browser for specific functions, but not launch the browser for other functions, as to save the resources and time it takes in opening and closing the browser. Is it possible for me to have such a file?
You best option is probably to create two separate test suites, one that uses uses Selenium commands and the other that does not use any Selenium functionality..
class BrowserTests extends PHPUnit_Extensions_SeleniumTestCase
{
protected function setUp()
{
$this->setBrowser('*firefox /usr/lib/firefox/firefox-bin');
...
}
public function testOne()
{
...
}
...
}
class NonBrowsterTests extends PHPUnit_Framework_TestCase
{
protected function setUp()
{
...
}
public function testOne
{
...
}
...
}
Figured out a custom solution using PHPUnit annotations (and wrote a blog post about it!)
http://blog.behance.net/dev/custom-phpunit-annotations
EDIT: Adding some code here, as to make my answer more complete :)
In short, use custom annotations. In your setUp(), parse the doc block to grab annotations, and tag tests with different qualities. This would allow you to tag certain tests to run with a browser, and certain tests to run without.
protected function setUp() {
$class = get_class( $this );
$method = $this->getName();
$reflection = new ReflectionMethod( $class, $method );
$doc_block = $reflection->getDocComment();
// Use regex to parse the doc_block for a specific annotation
$browser = self::parseDocBlock( $doc_block, '#browser' );
if ( !self::isBrowser( $browser )
return false;
// Start Selenium with the specified browser
} // setup
private static function parseDocBlock( $doc_block, $tag ) {
$matches = array();
if ( empty( $doc_block ) )
return $matches;
$regex = "/{$tag} (.*)(\\r\\n|\\r|\\n)/U";
preg_match_all( $regex, $doc_block, $matches );
if ( empty( $matches[1] ) )
return array();
// Removed extra index
$matches = $matches[1];
// Trim the results, array item by array item
foreach ( $matches as $ix => $match )
$matches[ $ix ] = trim( $match );
return $matches;
} // parseDocBlock