PHPunit Bug Mock 断言二进制字符串

PHPunit Bug Mock assert Binary String

我发现关于 phpunit Mock 的奇怪结果

我问自己这个错误是否是由 serialize()

中的 UTF8 字符引起的

当使用 privateprotected 模拟 return 序列化对象时,像这样

Expectation failed for method name is equal to <string:error> when invoked zero or more times
Parameter 0 for invocation Bar::error(Binary String: 0x4f3a333a22466...b4e3b7d) does not match expected value.
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-'O:6:"Foo":1:{s:5:"title";N;}'
+Binary String: 0x4f3a333a22466f6f223a313a7b733a32303a22002a00666f6f50726f74656374656456616c7565223b4e3b7d

代码

class Foo
{
    public $fooPublicValue;
    protected $fooProtectedValue; //BREAK
    private $fooPrivateValue;     //BREAK
}

class Bar
{
    public function error($message)
    {
        //some process
    }
}

class Baz
{
    public function exec(Bar $bar)
    {
        $bar->error(serialize(new Foo()));
    }
}

class BazTest extends \PHPUnit_Framework_TestCase
{
    public function testExec()
    {
        $loggerMock = $this->getMockBuilder('Bar')
            ->getMock();

        $loggerMock
            ->method('error')
            ->with($this->equalTo('O:6:"Foo":1:{s:5:"title";N;}'));

        (new Baz())->exec($loggerMock);
    }
}

PHP docs 中所述,私有成员和受保护成员在序列化期间以 * 或 class 名称作为前缀。这些前置值将在两边都有空字节。

更详细一点:

这意味着,虽然肉眼看不到,但字符串的实际字节表示发生了变化。例如,可以使用 bin2hex:

class Foo
{
    public $value;
    protected $one;
    private $two;
}

$serialized = serialize(new Foo());
$expected = 'O:3:"Foo":3:{s:5:"value";N;s:6:"*one";N;s:8:"Footwo";N;}';

echo $serialized; // O:3:"Foo":3:{s:5:"value";N;s:6:"*one";N;s:8:"Footwo";N;}
echo $expected;   // O:3:"Foo":3:{s:5:"value";N;s:6:"*one";N;s:8:"Footwo";N;}

echo bin2hex($serialized);  // 4f3a333a22466f6f223a333a7b733a353a2276616c7565223b4e3b733a363a22002a006f6e65223b4e3b733a383a2200466f6f0074776f223b4e3b7d
echo bin2hex($expected);    // 4f3a333a22466f6f223a333a7b733a353a2276616c7565223b4e3b733a363a222a6f6e65223b4e3b733a383a22466f6f74776f223b4e3b7d

你可以清楚地看到一根绳子比另一根长。如果您查看描述受保护 $one 属性 的片段,您可以发现空字节:

s:6:"*one";N

733a363a22002a006f6e65223b4e
733a363a22  2a  6f6e65223b4e

现在您知道了差异的来源,让我们开始解决您的问题。

解决方案

通过实现 Serializable 接口,您可以使用 serialize()unserialize() 来 return 一个代表您的对象的序列化数组。由于数组的所有值都是 public,因此不会在字符串中插入空字节,因此您可以安全地比较它。您的问题已解决:

class Foo implements Serializable
{
    public $value;
    protected $one;
    private $two;

    public function serialize()
    {
        return serialize([$this->value, $this->one, $this->two]);
    }

    public function unserialize($str)
    {
        list($this->value, $this->one, $this->two) = unserialize($str);
    }
}

// true
var_dump(serialize(new Foo()) === 'C:3:"Foo":24:{a:3:{i:0;N;i:1;N;i:2;N;}}');

希望对您有所帮助。