jasny / phpunit-extension
PHPUnit 的额外功能(回调模拟,预期警告等)
v0.5.1
2024-08-30 08:11 UTC
Requires
- php: >=8.1
- phpunit/phpunit: >= 10.1, < 12.0
Requires (Dev)
- phpstan/phpstan: >= 1.12, < 2.0
- phpstan/phpstan-strict-rules: >= 1.6, < 2.0
- squizlabs/php_codesniffer: ^3.5
README
PHPUnit 的额外功能。
- 回调模拟 - 确认回调是否以正确的参数被调用。
- 预期警告 - 确认触发警告/通知并继续运行。
- 在上下文中 - 访问私有/受保护的私有方法和属性。
- 连续调用 -
withConsecutive()
方法的替代品。
安装
composer require jasny/phpunit-extension
使用方法
回调模拟
MockObject createCallbackMock(InvocationOrder $matcher, array|Closure $assert = null, $return = null)
该方法接受预期的参数数组作为返回值或一个 Closure
。如果提供了一个 Closure
,则它将使用一个 InvocationMocker
来调用,这可以用于更复杂的匹配。
use Jasny\PHPUnit\CallbackMockTrait; use PHPUnit\Framework\MockObject\Builder\InvocationMocker; use PHPUnit\Framework\TestCase; class MyTest extends TestCase { use CallbackMockTrait; public function testSingleCall() { $callback = $this->createCallbackMock($this->once(), ['abc'], 10); function_that_invokes($callback); } public function testConsecutiveCalls() { $callback = $this->createCallbackMock( $this->exactly(2), function(InvocationMocker $invoke) { $invoke ->withConsecutive(['abc'], ['xyz']) ->willReturnOnConsecutiveCalls(10, 42); } ); function_that_invokes($callback); } }
安全模拟
SafeMocksTrait
重写了 createMock
方法以禁用自动返回值生成。这意味着如果调用未配置的方法,测试将失败。
use Jasny\PHPUnit\SafeMocksTrait; use PHPUnit\Framework\TestCase; class MyTest extends TestCase { use SafeMocksTrait; public function test() { $foo = $this->createMock(Foo::class); $foo->expects($this->once())->method('hello'); $foo->hello(); $foo->bye(); } }
在上面的示例中,方法 bye()
未配置为 $foo
。通常测试会成功,但使用 SafeMocksTrait
,这个测试将导致失败。
Return value inference disabled and no expectation set up for Foo::bye()
预期警告
ExpectedWarningTrait
重写了以下方法:
expectNotice()
expectNoticeMessage(string $message)
expectNoticeMessageMatches(string $regexp)
expectWarning()
expectWarningMessage(string $message)
expectWarningMessageMatches(string $regexp)
expectDeprecation()
expectDeprecationMessage(string $message)
expectDeprecationMessageMatches(string $regexp)
以下是一个示例函数;
function my_old_func(float $a) { trigger_error("Use my_new_func() instead", E_USER_DEPRECATED); return (int)($a * 100); }
PHPUnit 将通知和警告转换为异常。虽然您可以通过如 expectDeprecation
等方法捕获这些异常,但通知/警告之后的任何代码都没有运行,因此不可测试。
以下测试成功,而它应该失败。
use PHPUnit\Framework\TestCase; class MyTest extends TestCase { public function test() { $this->expectDeprecation(); $result = my_old_func(0.42); // <-- Will exit on deprecation $this->assertEquals(89, $result); // This should fail, but is never executed } }
ExpectedWarningTrait
设置一个自定义的错误处理器,该处理器捕获预期的警告和通知,而不将它们转换为异常。代码将继续运行。
在其他所有断言成功之后,代码将检查是否有任何未被触发的预期警告/通知。
use Jasny\PHPUnit\ExpectWarningTrait; use PHPUnit\Framework\TestCase; class MyTest extends TestCase { use ExpectWarningTrait; public function test() { $this->expectDeprecation(); $result = my_old_func(0.42); // <-- Will NOT exit $this->assertEquals(89, $result); // Will fail } }
在上下文中
mixed inContextOf(object $object, \Closure $function)
函数在给定对象的上下文中被调用。这允许调用私有和受保护的私有方法和获取或设置私有和受保护的属性。
use Jasny\PHPUnit\InContextOfTrait; use PHPUnit\Framework\TestCase; class MyTest extends TestCase { use InContextOfTrait; public function testCallPrivateMethod() { $object = new MyObject(); $result = $this->inContextOf($object, fn($object) => $object->privateMethod('foo', 'bar')); $this->assertEquals($result, 'foo-bar'); } public function testGetPrivateProperty() { $value = $this->inContextOf($object, fn($object) => $object->privateProperty); $this->assertEquals($value, 999); } /** Alternatively, do the assertion in the closure */ public function testAssertPrivateProperty() { $this->inContextOf($object, fn($object) => $this->assertEquals($object->privateProperty, 999)); } public function testSetPrivateProperty() { $this->inContextOf($object, fn($object) => $object->privateProperty = 42); } }
注意:您应该只通过公共方法和属性进行测试。当您需要访问私有方法或属性来执行测试时,您的代码架构可能存在一些问题。
连续调用
ConsecutiveTrait
是 PHPUnit 的已删除 withConsecutive
方法的替代品。
use Jasny\PHPUnit\ConsecutiveTrait; use PHPUnit\Framework\TestCase; class MyTest extends TestCase { use ConsecutiveTrait; public function test() { $mock = $this->createMock(MyClass::class); $mock->expects($this->exactly(2)) ->method('foo') ->with(...$this->consecutive( ['a', 1], ['b', 2], )) ->willReturnOnConsecutiveCalls(10, 42); $this->assertEquals(10, $mock->foo('a', 1)); $this->assertEquals(42, $mock->foo('b', 2)); } }