sebastianbergmann/phpunit-mock-objects

Mocking DateTimeInterface objects distorts space and time

dinamic opened this issue · 10 comments

Q A
phpunit-mock-objects version x.y.z
PHPUnit version 7.2.6
PHP version 7.2.7
Installation Method Composer
Tested with xdebug 2.6.0, phpdbg 7.2.7

While working on trying to get some adequate unit tests I stumbled upon a case where I had to mock DateTimeInterface objects. Seems like there's something weird going on with those. Consider the example below. Both should fail, alas one succeeds.

What's going on? Is this expected behaviour? Sounds like DateTimeInterface objects are messed up so bad they distort the universe around them.

P.S This may not be the correct repository. Maybe I should move this one to phpunit? I don't seem to have phpunit-mock-objects installed.

# composer show | grep -i phpunit

phpunit/php-code-coverage                     6.0.7              Library that provides collection, processing, and rendering functionality for PHP code coverage information.
phpunit/php-file-iterator                     2.0.1              FilterIterator implementation that filters files based on a list of suffixes.
phpunit/php-text-template                     1.2.1              Simple template engine.
phpunit/php-timer                             2.0.0              Utility class for timing
phpunit/php-token-stream                      3.0.0              Wrapper around PHP's tokenizer extension.
phpunit/phpunit                               7.2.6              The PHP Unit Testing framework.
symfony/phpunit-bridge                        v4.1.1             Symfony PHPUnit Bridge
<?php

class DateTimeInterfaceMockingTest extends \PHPUnit\Framework\TestCase
{
    public function testExecuteWillNotFailBecauseTrueEqualsFalse(): void
    {
        $mockDateTime = $this->createMock(DateTimeInterface::class);

        $this->assertFalse(true);
        $this->assertTrue(false);
    }

    public function testExecuteWillFailBecauseTrueEqualsFalseAndMockingPositionDoesMatter(): void
    {
        $this->assertFalse(true);
        $this->assertTrue(false);

        $mockDateTime = $this->createMock(DateTimeInterface::class);
    }

    public function testExecuteWillFailBecauseTrueDoesNotEqualsFalse(): void
    {
        $this->assertFalse(true);
        $this->assertTrue(false);
    }
}
# vendor/bin/phpunit ./test.php --no-coverage

PHPUnit 7.2.6 by Sebastian Bergmann and contributors.

Testing DateTimeInterfaceMockingTest
.FF                                                                 3 / 3 (100%)

Time: 462 ms, Memory: 14.00MB

There were 2 failures:

1) DateTimeInterfaceMockingTest::testExecuteWillFailBecauseTrueEqualsFalseAndMockingPositionDoesMatter
Failed asserting that true is false.

/Users/nikola/Projects/Jiminny/app/test.php:15

2) DateTimeInterfaceMockingTest::testExecuteWillFailBecauseTrueDoesNotEqualsFalse
Failed asserting that true is false.

/Users/nikola/Projects/Jiminny/app/test.php:23

FAILURES!
Tests: 3, Assertions: 2, Failures: 2.

If this only happens with PHPUnit 7.2 then, yes, this should be reported in the main PHPUnit repository. If this also happens with PHPUnit 6.5 then this is the right repository.

Just to iterate on this. Seems like mocking DateTime and DateTimeImmutable works fine. Mocking towards DateTimeinterface seems to be the problem here.

While this might be a bug (I have not investigated it yet), I personally would not stub/mock DateTimeImmutable (or DateTime) as I do not stub/mock value objects.

Maybe @derickr can provide insight why DateTimeInterface might be special.

It may be worth mentioning that it fails using both xdebug and phpdbg, so is not specific to either.

How would this be related to code coverage data collection? I pinged @derickr on this because he maintains ext/date, not because he maintains Xdebug :-)

Tried ignoring the current configuration that I've got and something interesting happened.

PHP Fatal error:  DateTimeInterface can't be implemented by user classes in /Users/nikola/Projects/RandomProject/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator.php(603) : eval()'d code on line 1

Full log here: https://pastebin.com/iHitmD23

Here's my phpunit.xml.dist:

<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
         backupStaticAttributes="false"
         bootstrap="bootstrap/autoload.php"
         colors="true"
         convertErrorsToExceptions="true"
         convertNoticesToExceptions="true"
         convertWarningsToExceptions="true"
         processIsolation="true"
         stopOnFailure="false">
    <testsuites>
        <testsuite name="Application Test Suite">
            <directory suffix="Test.php">./tests/unit</directory>
        </testsuite>
    </testsuites>
    <filter>
        <whitelist processUncoveredFilesFromWhitelist="true">
            <directory suffix=".php">./app</directory>
        </whitelist>
    </filter>
    <php>
        <env name="APP_ENV" value="testing"/>
        <env name="CACHE_DRIVER" value="array"/>
        <env name="SESSION_DRIVER" value="array"/>
        <env name="QUEUE_DRIVER" value="sync"/>
        <ini name="error_reporting" value="E_STRICT | E_ALL" />
    </php>
    <logging>
        <log type="coverage-clover" target="build/coverage.xml"/>
    </logging>
    <listeners>
        <listener class="Symfony\Bridge\PhpUnit\SymfonyTestsListener" />
    </listeners>
</phpunit>

Seems like the error_reporting is silencing the error.

Using 32767 instead of the value E_STRICT | E_ALL makes the fatal error apparent.

I guess I should close this one as it seems like a misconfiguration on my part more than anything else.

@sebastianbergmann, is support for php constants in the ini section of the config file on the roadmap of features to expect?

When I was faced with the issue I was trying to figure out how to mock DateTimeInterface objects while comparing them. Do you happen to know what method do the comparison operators invoke? Or would they call one, actually. It very well could be some php internals logic.

Just use "-1" everywhere, for error_reporting :-)