I am writing test cases and here is a question I have.
So say I am testing a simple function someClass::loadValue($value)
The normal test case is easy, but assume when passing in null or -1 the function call generates a PHP Warning, which is considered a bug.
The question is, how do I write my PHPUnit test case so that it succeeds when the functions handles null/-1 gracefully, and fail when there is a PHP Warning thrown?
PHPUnit_Util_ErrorHandler::handleError() throws one of several exception types based on the error code:
PHPUnit_Framework_Error_Notice for E_NOTICE, E_USER_NOTICE, and E_STRICT
PHPUnit_Framework_Error_Warning for E_WARNING and E_USER_WARNING
PHPUnit_Framework_Error for all others
You can catch and expect these as you would any other exception.
/**
* #expectedException PHPUnit_Framework_Error_Warning
*/
function testNegativeNumberTriggersWarning() {
$fixture = new someClass;
$fixture->loadValue(-1);
}
I would create a separate case to test when the notice/warning is expected.
For PHPUnit v6.0+ this is the up to date syntax:
use PHPUnit\Framework\Error\Notice;
use PHPUnit\Framework\Error\Warning;
use PHPUnit\Framework\TestCase;
class YourShinyNoticeTest extends TestCase
{
public function test_it_emits_a_warning()
{
$this->expectException(Warning::class);
file_get_contents('/nonexistent_file'); // This will emit a PHP Warning, so test passes
}
public function test_it_emits_a_notice()
{
$this->expectException(Notice::class);
$now = new \DateTime();
$now->whatever; // Notice gets emitted here, so the test will pass
}
}
What worked for me was modifying my phpunit.xml to have
<phpunit
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
strict="true"
>
</phpunit>
The key was to use strict="true" to get the warnings to result in a failed test.
You can also write a phpunit.xml file (on your tests dir) with this:
<phpunit
convertErrorsToExceptions="true"
convertNoticesToExceptions="false"
stopOnFailure="false">
</phpunit>
Using Netsilik/BaseTestCase (MIT License) you can test directly for triggered Errors/Warnings, without converting them to Exceptions:
composer require netsilik/base-test-case
Testing for an E_USER_NOTICE:
<?php
namespace Tests;
class MyTestCase extends \Netsilik\Testing\BaseTestCase
{
/**
* {#inheritDoc}
*/
public function __construct($name = null, array $data = [], $dataName = '')
{
parent::__construct($name, $data, $dataName);
$this->_convertNoticesToExceptions = false;
$this->_convertWarningsToExceptions = false;
$this->_convertErrorsToExceptions = true;
}
public function test_whenNoticeTriggered_weCanTestForIt()
{
$foo = new Foo();
$foo->bar();
self::assertErrorTriggered(E_USER_NOTICE, 'The warning string');
}
}
Hope this helps someone in the future.
public function testFooBar(): void
{
// this is required
$this->expectWarning();
// these are optional
$this->expectWarningMessage('fopen(/tmp/non-existent): Failed to open stream: No such file or directory');
$this->expectWarningMessageMatches('/No such file or directory/');
fopen('/tmp/non-existent', 'rb');
}
Make SomeClass throw an error when input is invalid and tell phpUnit to expect an error.
One method is this:
class ExceptionTest extends PHPUnit_Framework_TestCase
{
public function testLoadValueWithNull()
{
$o = new SomeClass();
$this->setExpectedException('InvalidArgumentException');
$this->assertInstanceOf('InvalidArgumentException', $o::loadValue(null));
}
}
See documentation for more methods.
Related
I have the following exception:
<?php
namespace App\Exception;
class LimitReachedException extends \Exception
{
private ?\DateTime $resumeAt;
...getter/setter..
}
My PHPUnit check for this exception like this:
$this->expectException(LimitReachedException::class);
How can I check that a certain value is stored in the $resumeAt property as well?
Even though PHPUnit has a expectExceptionObject method that allows passing an exception instance, that is just a shortcut to expectExceptionMessage, expectException and expectExceptionCode.
One way to achieve your assertion as of now (current version of PHPUnit being 9.5.27) is to instead of using PHPUnit's methods of expecting that exception is to catch it yourself and then assert the different properties:
function testException () {
$expectedException = null;
try {
$foo->doSomething();
} catch (LimitReachedException $e) {
$expectedException = $e;
}
// Put your assertions outside of the `catch` block, otherwise
// your test won't fail when the exception isn't thrown
// (it will turn risky instead because there are no assertions)
$this->assertInstanceOf(LimitReachedException::class, $expectedException);
$this->assertSame('myExceptionProperty', $expectedException->getProperty());
}
With the following class
declare(strict_types=1);
abstract class IntValueObject
{
public function __construct(protected int $value)
{
}
}
and the test
declare(strict_types=1);
final class IntValueObjectTest extends TestCase
{
public function testWithNotValidValue(): void
{
$value = '1';
$this->expectException(\TypeError::class);
$this->getMockForAbstractClass(IntValueObject::class, [$value]);
}
}
return
Api\Tests\Shared\Domain\ValueObject\IntValueObjectTest::testWithNotValidValue
Failed asserting that exception of type "TypeError" is thrown.
If I change $value from '1' to 'foo' if it passes the test.
We use PHP 8, and in production, if the value '1' is passed it would give TypeError, why doesn't this happen in the test?
Thanks in advance.
ORIGIN OF THE "PROBLEM"
https://bugs.php.net/bug.php?id=81258
POSSIBLE SOLUTION
declare(strict_types=1);
final class IntValueObjectTest extends TestCase
{
public function testWithNotValidValue(): void
{
$value = '1';
$this->expectException(\TypeError::class);
new class($value) extends IntValueObject {};
}
}
One explanation I can imagine is that during the test, IntValueObject::__construct('1') is called from code that is not using declare(strict_types=1); and therefore the string '1' is being coerced to integer 1. No TypeError is thrown in that case (but it would for string 'foo' - as you describe the behaviour in your question).
The Cause
The cause is using a generated mock:
<?php declare (strict_types = 1);
...
$this->expectException(\TypeError::class);
$this->getMockForAbstractClass(IntValueObject::class, [$value]);
...
To not have a TypeError in this situation is likely unexpected to you as you have scalar strict types but still see the type-coercion of the string '1' to integer 1 for the constructors' first parameter.
The Mismatch
However the TypeError is only thrown when the code calling IntValueObject::__construct(int $value) has declare(strict_types=1).
While the test-code has declare(strict_types=1) it must not be that code where the constructor method is called - as no TypeError is thrown.
For Real
Behind the scenes $this->getMockForAbstractClass(...); uses an Instantiator from the Doctrine project which is making use of PHP reflection (meta-programming). As those methods are all internal code, declare(strict_types=1) is not effective and there is no TypeError anymore.
Compare with the following code-example:
<?php declare(strict_types=1);
class Foo {
public function __construct(int $integer) {
$this->integer = $integer;
}
}
try {
$foo = new Foo('1');
} catch (TypeError $e) {
} finally {
assert(isset($e), 'TypeError was thrown');
assert(!isset($foo), '$foo is unset');
}
$foo = (new ReflectionClass(Foo::class))->newInstance('1');
var_dump($foo);
When executed with assertions enabled, the output is the following:
object(Foo)#3 (1) {
["integer"]=>
int(1)
}
Within the try-block, the TypeError is thrown with new as you expect it.
But afterwards when instantiating with PHP reflection it is not.
(see as well https://3v4l.org/aZTJl)
The Remedy
Add a class to your test-suite that is really mocking the abstract base class and place it next to the test of it so you can easily use it:
<?hpp declare(strict_types=1);
class IntValueObjectMock extends IntValueObject
{...}
Then use IntValueObjectMock in your test:
$value = '1';
$this->expectException(\TypeError::class);
new IntValueObjectMock($value);
Alternatively use anonymous class when extending it is straight forward:
$value = '1';
$this->expectException(\TypeError::class);
new class($value) extends IntValueObject {};
Or apply type-checks on the constructor method your own, either with PHP reflection or run static code-analysis which has the benefit that it can detect such issues already without instantiating - so no additional test-code is involved.
I'm currently trying to pass data from my data provider to the setUp()-method in PHPUnit.
Background: I am using PHPUnit for running frontend-tests in different browsers. The browser should be defined inside the data provider and needs to be known by the setUp()-method.
I understand, that a data provider initially is executed before the setUp()-method (as setUpBeforeClass()) is called. Therefore setUp()-data can not be passed to a data provider. But it should work the other way round, shouldn't it?
Does PHPUnit generate its own temporarily testclasses with data from the data provider "integrated"?
Of course: a workaround could be, to read the XML-file in the setUp()-method again. But that's the last option, I'd consider...
EDIT: Provided a small snippet:
part of dataProvider():
public function dataProvider()
{
$this->xmlCnf = $data['config'];
var_dump($this->xmlCnf); // array with config is exposed
// [...]
}
And the setUp()-method:
protected function setUp()
{
var_dump($this->xmlCnf); // NULL
//[...]
}
In case this is useful to anyone:
The following code should work:
public function dataProvider()
{
return [ [ /* dataset 1 */] , ... ]
}
protected setUp() {
parent::setUp();
$arguments = $this->getProvidedData();
// $arguments should match the provided arguments for this test case
}
/**
* #dataProvider dataProvider
*/
public function testCase(...$arguments) {
}
The getProvidedData method seems to have been available since PHPUnit 5.6 (which was either shortly before or after this question was originally asked)
we can make the xmlCnf to static
private static $xmlCnf;
public function provider(){
self::$xmlCnf = 'hello';
var_dump(self::$xmlCnf); //hello
return [...];
}
public function setUp() {
var_dump(self::$xmlCnf); //hello
parent::setUp();
}
I'm trying to make custom routeloader according to http://symfony.com/doc/current/cookbook/routing/custom_route_loader.html
my code looks like this
//the routeloader:
//the namespace and use code ....
class FooLoader extends Loader{
private $loaded = false;
private $service;
public function __construct($service){
$this->service = $service;
}
public function load($resource, $type=null){
if (true === $this->loaded)
throw new \RuntimeException('xmlRouteLoader is already loaded');
//process some routes and make $routeCollection
$this->loaded = true;
return $routeCollection;
}
public function getResolver()
{
// needed, but can be blank, unless you want to load other resources
// and if you do, using the Loader base class is easier (see below)
}
public function setResolver(LoaderResolverInterface $resolver)
{
// same as above
}
function supports($resource, $type = null){
return $type === 'xmlmenu';
}
}
//the service definition
foo.xml_router:
class: "%route_loader.class%"
arguments: [#foo.bar_service] //this service and the injection has been tested and works.
tags:
- { name: routing.loader }
//the routing definitions
//routing_dev.yml
_foo:
resource: "#FooBarBundle/Resources/config/routing.yml"
-----------------------------
//FooBarBundle/Resources/config/routing.yml
_xml_routes:
resource: .
type: xmlmenu
and when I try to access any route I get the exception:
RuntimeException: xmlRouteLoader is already loaded
which is the exception I defined if the loader is loaded multiple times.So why does it try to load this loader more than once? and I'm pretty sure I've defined it only there.
Actually the answer was quite simple.it seems like this method only supports one level of imports.I only needed to put the _xml_routes directly under routing_dev.yml, otherwise it somehow winds out in a loop.explanations to why that is are appreciated.
I have a functional test that creates and persists some things in the database and I want to test that the correct number of items was inserted (there is a scenario where it currently inserts two instead of one).
In the controller everything seems to work and if I use the code below (in the controller) to debug it, I get the expected (wrong) value of "2":
$em = $this->getDoctrine()->getManager();
$fooRepo = $em->getRepository('CompanyProjectBundle:Foo');
$foos = $fooRepo->retrieveByBar(3);
echo count($foos); // Gives a result of 2
However, if I try something similar from within my Test class I get zero...
/**
* {#inheritDoc}
*/
protected function setUp()
{
static::$kernel = static::createKernel();
static::$kernel->boot();
$this->em = static::$kernel->getContainer()
->get('doctrine')
->getManager()
;
$this->em->getConnection()->beginTransaction();
}
/**
* {#inheritDoc}
*/
protected function tearDown()
{
parent::tearDown();
$this->em->getConnection()->rollback();
$this->em->close();
}
public function testFooForm()
{
// ... do some testing
$fooRepo = $this->em->getRepository('CompanyProjectBundle:Foo');
$foos = $fooRepo->retrieveByBar(3);
echo count($foos); // gives a result of ZERO
// ... more happens later
}
Is it getting a different entity manager or something like that? Should I be using some other method to get hold of the correct EM so I can then view the same data that the app is running from?
Everything's running inside a transaction (which is rolled back when the test client is destroyed), but that happens after the snippet shown above.
Ah... solved my own problem. I think I was getting the wrong EntityManager. I fixed it by getting the EntityManager via the client's container instead of the kernel's one:
public function testFooForm()
{
// ... do some testing
$clientEm = $client->getContainer()->get('doctrine.orm.entity_manager');
$fooRepo = $clientEm->getRepository('CompanyProjectBundle:Foo');
$foos = $fooRepo->retrieveByBar(3);
echo count($foos); // gives the correct result of 2
// ... more happens later
}