phpunit: Serialization of 'Closure' is not allowed when using @runInSeparateProcess

Q A
PHPUnit version 9.2.5
PHP version 7.4.7
Installation Method Composer

Summary

One of our tests started failing with Serialization of 'Closure' is not allowed. When I removed the @runInSeparateProcess (which I have to keep as workaround for https://bugs.php.net/bug.php?id=79646) I got an entirely different error which helped me find the problem in the test.

Apparently there is some issue with process isolation - the data that are serialized to be sent back to the original process contain a Closure and the serialization fails as a result.

Note: This has nothing to do with https://github.com/sebastianbergmann/phpunit/issues/2739 - that’s about sending data into the test, this is about getting data back.

I’m not sure what data are serialized and where the serialization happens so I’m unable to provide more details and a reproducer for now. Can you point me where in PHPUnit’s source code this serialization happens? (Using -v and --debug didn’t get me a stack trace.)

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Comments: 15 (2 by maintainers)

Commits related to this issue

Most upvoted comments

I’m going to add a little more context, as I’ve just run across the same issue, and have no idea how to change my test to work.

The basic gist of it is:


/**
 * @runInSeparateProcess
 * @preserveGlobalState disabled
 * @dataProvider somethingProvidingAStringMessage
 */
public function testMockingWithConstraints(string $message): void
{
    $mock = $this->createMock(AClassDefiningWriteLine::class);
    $mock
        ->expects($this->once())
        ->method('writeLine')
        ->with($this->stringContains($message));

    $mock->writeLine('<info>' . $message . '</info>');
}

The problem with this statement, @sebastianbergmann :

Do not call assertions in closures then, sorry.

is that the above example demonstrates a documented usage of mock objects, namely, using one of the various constraint methods to provide an assertion to the mock object. But it does not work in this scenario, due to the serialization issues.

I think the issue is valid and should be re-opened.

Do not call assertions in closures then, sorry.

This appears to happen when an assertion called within a Closure fails.

Here’s an example that reproduces this:

/**
 * @runInSeparateProcess
 * @preserveGlobalState disabled
 */
public function testSomething(): void
{
    $mock = $this->createMock(\DateTime::class);
    $mock->expects($this->once())
        ->method('format')
        ->with($this->callback(function ($arg1): bool {
            $this->assertSame('foo', $arg1);

            return true;
        }));

    $mock->format('bar');
}

When run as-is, this produces the following fatal error:

PHP Fatal error:  Uncaught Exception: Serialization of 'Closure' is not allowed in Standard input code:89
Stack trace:
#0 Standard input code(89): serialize(Array)
#1 Standard input code(122): __phpunit_run_isolated_test()
#2 {main}
  thrown in Standard input code on line 89
Fatal error: Uncaught Exception: Serialization of 'Closure' is not allowed in Standard input code:89
Stack trace:
#0 Standard input code(89): serialize(Array)
#1 Standard input code(122): __phpunit_run_isolated_test()
#2 {main}
  thrown in Standard input code on line 89

If you change the the argument value for $mock->format() to 'foo', then it passes normally. If you leave it as 'bar' but remove the annotations, you see:

Expectation failed for method name is "format" when invoked 1 time(s)
Failed asserting that two strings are identical.
--- Expected
+++ Actual
@@ @@
-'foo'
+'bar'

I wrote the patch: mpyw/phpunit-patch-serializable-comparison

Awesome that works for me ! Thanks !

I wrote the patch: mpyw/phpunit-patch-serializable-comparison

Can confirm, works! Very nice, thank you!