How to check for Exception property with PHPunit - symfony

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());
}

Related

PhpUnit does not identify TypeError with string numbers and Abstract Class

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.

Symfony services.yaml handle exception

In my services.yaml, I'm having this code:
services:
# prepare database
DatabaseProvider:
class: Kepler\Providers\DatabaseProvider
arguments: ['%database.reader.dsn%', '%database.reader.username%', '%database.reader.password%']
ReaderPDO:
class: PDO
factory: ['#DatabaseProvider', generateDatabaseConnection]
WriterPDO:
class: PDO
factory: ['#DatabaseProvider', generateDatabaseConnection]
And DatabaseProvider:generateDatabaseConnection
public function generateDatabaseConnection() {
try {
return new PDO($this->host, $this->username, $this->password);
} catch (Exception $e) {
return new PDO($this->host, $this->username, $this->password);
$_GLOBALS['db_error'] = true;
}
}
So my question is here: There will be cases where the PDO connection is not working/failing. So how I catch this exception and return the general 404 page (This is the page where I don't need any db connection)?
Some basic analysis:
I believe this is failing during symfony booting up
Even if I return null, or return nothing in the catch, the code will keep continue autowiring, which will then fail in Controller that requires DB connection
having a return in a catch block should be considered harmful if it's exactly the same thing to be returned as in the try block.
Setting a global value is really old-school and not really the symfony way. Instead, you would throw a specific exception (or just not catch the PDO one), and add an event listener for the kernel exception that will result from that, where you may look for that specific exception and return the proper error page you want.
If you just want the 404 page, you can throw the NotFoundHttpException or maybe more appropriate the ServiceUnavailableException ...

powermock running with MockitToRunner - getting matchers error

#RunWith(MockitoJUnitRunner.class)
public class MessageDataTest {
#InjectMocks
MessageData messageData;
#Test
public void testingMethod() throws Exception {
MessageData data = PowerMockito.spy(messageData); //passing the mocked object for spy
PowerMockito.when(data,"isMessageContains",anyString(),any()).thenReturn(true); // throwing exception here
MessageDataResponse response = messageFormatterData.constructData(messageItems);
assertNotNull(response);
}
}
MessageData.java
private boolean isMessageContains(String name, MessageResponse messageResponse) {
for (::) {
some logic
return true;
}
}
return false;
}
when I run the test case, I'm getting the below error,
org.mockito.exceptions.misusing.InvalidUseOfMatchersException: Misplaced argument matcher detected here
You cannot use argument matchers outside of verification or stubbing. Examples of correct usage of argument matchers: when(mock.get(anyInt())).thenReturn(null); doThrow(new RuntimeException()).when(mock).someVoidMethod(anyObject()); verify(mock).someMethod(contains("foo"))
Also, this error might show up because you use argument matchers with methods that cannot be mocked. Following methods cannot be stubbed/verified: final/private/equals()/hashCode(). Mocking methods declared on non-public parent classes is not supported
I want to mock this private "isMessageContains" to return always true.

PHPUnit: How to force program exit on specific error

How can I force PHPUnit to stop running completely and exit when a specific condition (an error of my own choosing) is met? Effectively, what I need is something like the below, except that in reality PHPUnit traps the exit() and continues running instead of exiting.
// PHPUnit does not alter existing but empty env vars, so test for it.
if (strlen(getenv('APP_HOME')) < 1) {
$this->fail('APP_HOME set but empty.');
exit(1); // <-- Does not work.
}
Note: I want to continue running normally for other errors and failures, hence setting stopOnError="true" or stopOnFailure="true" in my XML file is not what I need.
I think you can achieve this by doing a few overrides and adding some custom behaviour to a base test case class.
EDIT:
As found by the OP after running the below code, calling exit(1); rather than $result->stop() will cause correct termination of the test at that point.
Try the following:
class MyBaseTestCase extends \PHPUnit_Framework_TestCase
{
// Test this flag at every test run, and stop if this has been set true.
protected $stopFlag = false;
// Override parent to gain access to the $result so we can call stop()
public function run(\PHPUnit_Framework_TestResult $result = null)
{
$result = parent::run($result);
if ($this->stopFlag === true)
{
//$result->stop(); // Stop the test for this special case
exit(1); // UPDATED: This works to terminate the process at this point
}
return $result; // return as normal
}
}
Then in a test case class:
class MyTestCase extends MyBaseTestCase
{
public function testThisStopsPhpunit()
{
if (strlen(getenv('APP_HOME')) < 1) {
$this->fail('APP_HOME set but empty.');
$this->stopFlag = true; // Stop further processing if this occurs
}
}
}

ObjectDisposedException While using Include - why?

My page calls a Services layer method that uses a Generic Repository "Find" method. In the services layer method, I do the following:
using (IUnitOfWork unitOfWork = new DBContext())
{
GenericRepository<Operator> operatorRepos = new GenericRepository<Operator>(unitOfWork);
{
try
{
var oper = operatorRepos.Find(o => o.OperatorID == operatorID).Include(o => o.cmn_Address).Single();
return oper;
}
catch (InvalidOperationException exc)
{
//handle exception
}
}
}
The Find method for my repository:
public IQueryable<T> Find(Func<T, bool> predicate)
{
return _objectSet.Where<T>(predicate).AsQueryable();
}
On the page, I try to access the cmn_address Navigation property of the Operator and I get the following error:
The ObjectContext instance has been disposed and can no longer be used for operations that require a connection.
I realize that this is caused by the using statement to dispose of the context, but I thought the Include method will eager load the cmn_Address object. I don't understand why this doesn't work as I expected.
You are using Func<> instead of Expression<Func<>> in your where condition. That makes it Linq-to-objects. This change is permanent. Calling AsQueryable doesn't make it Linq-to-entities again.

Resources