PHPunit Bug Mock 断言二进制字符串
PHPunit Bug Mock assert Binary String
我发现关于 phpunit Mock 的奇怪结果
我问自己这个错误是否是由 serialize()
中的 UTF8 字符引起的
当使用 private
或 protected
模拟 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;}}');
希望对您有所帮助。
我发现关于 phpunit Mock 的奇怪结果
我问自己这个错误是否是由 serialize()
当使用 private
或 protected
模拟 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;}}');
希望对您有所帮助。