box/shmock

PHPUnit 模拟的简写

v2.0.1-beta2 2017-02-28 21:24 UTC

README

这是什么?

Shmock 是一个创建PHPUnit模拟的流畅替代方案,它使用了EasyMock的mock/replay概念,但使用闭包来定义模拟的范围。

  <?php
  namespace Foo;

  /**
   * Here's a class we're trying to test yay.
   */
  class Foo
  {
  	private $foo = 0;
  	private $incrementing_service = null;

  	public function __construct(Incrementing_Service $incrementing_service)
  	{
  		$this->incrementing_service = $incrementing_service;
  	}

  	public function next_foo()
  	{
  		$this->foo = $this->incrementing_service->increment($this->foo);
  		return $this->foo;
  	}
  }

  /**
   * Our test case runs the same test case twice - once with the original PHPUnit mocking
   * syntax and a second time with Shmock syntax.
   */
  class Foo_Test extends PHPUnit_Framework_TestCase
  {
              use \Shmock\Shmockers; // This enables the use of the Shmock helper methods (replicated below)

              public function test_phpunit_original_mocking_syntax()
  	{
  		// this is the original PHPUnit mock syntax

  		$incrementing_service_mock = $this->getMock('\Foo\Incrementing_Service', array('increment'));
  		$incrementing_service_mock->expects($this->once())
  			->method('increment')
  			->with($this->equalTo(0))
  			->will($this->returnValue(1));

  		$foo = new Foo($incrementing_service_mock);
  		$this->assertEquals(1, $foo->next_foo(0));
  	}

  	/**
  	 * Create a shmock representation for $class_name and configure expected
  	 * mock interaction with $conf_closure
  	 * @return Shmock A fully configured mock object
  	 * @note You do not need this protected method if you use the Shmockers trait, shown above
  	 */
  	protected function shmock($class_name, $conf_closure)
  	{
  		return \Shmock\Shmock::create_class($this, $class_name, $conf_closure);
  	}

  	public function test_shmock_syntax()
  	{
  		// here's shmock. Neat huh?
  		$incrementing_service_mock = $this->shmock('\Foo\Incrementing_Service', function($shmock)
  		{
  			$shmock->increment(0)->return_value(1);
  		});

  		$foo = new Foo($incrementing_service_mock);
  		$this->assertEquals(1, $foo->next_foo(0));
  	}
  }

安装

Shmock 可以直接从 Packagist 安装。

"require": {
    "box/shmock": "1.0.0.x-dev"
}

或者,你可以将 Shmock.php 和 Shmockers.php 下载到你的测试目录中,然后运行

  require_once 'Shmock.php';

PHPUnit 应该已经包含在加载路径中,这样才能正常工作。

类型安全

Shmock 默认是类型安全的,并且会尝试告诉你何时使用了错误的模拟方法。当以下情况发生时,Shmock 会抛出错误:

  • 将静态方法模拟为实例方法或反之。
  • 将私有方法模拟为受保护或公共。
  • 模拟一个不存在的方法 并且 未提供 __call / __callStatic 魔术方法。

可以通过在 shmock 闭包中调用 $mock_object->disable_strict_method_checking() 来禁用这些检查。我们还计划支持参数和返回值检查,如果它符合尚未定义的 PHPDoc 规范。

文档

http://box.github.io/shmock/namespaces/Shmock.html

Shmock 全部功能列表

<?php
// This code could conceptually be part of a test method from the above Foo_Test class
  $inc_service = $this->shmock('\Foo\Incrementing_Service', function($my_class_shmock) // [1]
  {
  	$my_class_shmock->no_args_method(); // [2]
  	$my_class_shmock->two_arg_method('param1', 'param2'); // [3]
  	$my_class_shmock->method_that_returns_a_number()->return_value(100); // [4]
  	$my_class_shmock->method_that_gets_run_twice()->times(2); // [5]
  	$my_class_shmock->method_that_gets_run_any_times()->any(); // [6]

  	$my_class_shmock->method_puts_it_all_together('with', 'args')->times(2)->return_value(false);

  	$my_class_shmock->method_returns_another_mock()->return_shmock('\Another_Namespace\Another_Class', function($another_class) // [7]
  	{
  		$another_class->order_matters(); // [8]
  		$another_class->disable_original_constructor(); // [9a]
  		$another_class->set_constructor_arguments(1, 'Foo'); // [9b]

  		$another_class->method_dies_horribly()->throw_exception(new InvalidArgumentException()); // [10]

  		$another_class->method_gets_stubbed(1,2)->will(function(PHPUnit_Framework_MockObject_Invocation $invocation)
  		{
  			$a = $invocation->parameters[0];
  			$b = $invocation->parameters[1];
  			return $a + $b; // [11]
  		});
  	});

  	$my_class_shmock->shmock_class(function($Inc_Service)
  	{
  		$Inc_Service->my_static_method()->any()->return_value('This was returned inside the mock instance using the static:: prefix'); // [12]
  	});

  })

  $another_class = $this->shmock_class('\Another_Namespace\Another_Class', function($Another_Class) // [13]
  {
  	$Another_Class->a_static_method()->return_value(1);
  });
  1. Shmock 允许你在闭包内配置一个模拟对象。你使用的是一个感觉像真实对象一样的代理对象。
  2. 调用一个方法会设置一个期望,即它将被调用一次。
  3. 带参数调用一个方法会导致它期望在实际调用时使用这些参数。
  4. 你可以从特定的调用返回值。在示例中,当你调用该方法时,将返回值 100。
  5. 你可以指定方法将被调用的次数期望。默认情况下,期望一次。
  6. 或者你可以使用 any() 指定 "0 或更多" 次。
  7. 你可以嵌套 Shmock 调用,这让你可以优雅地定义你的模拟依赖关系。(如果你有两个方向的依赖关系,你总是可以直接 return_value($other_shmock) 并在其他地方定义它)
  8. 在对象级别上,你可以指定 "顺序很重要",这意味着函数调用的顺序也应被断言。在底层,这会自动使用 PHPUnit 的 at(N) 调用。
  9. 在定义构造函数参数方面,你有一些选项。a) 你可以选择禁用原始构造函数。通常 PHPUnit 会运行原始构造函数。b) 你可以使用给定的参数运行原始构造函数。
  10. 当方法被调用时,你可以抛出异常而不是返回值。
  11. 更加复杂的是,你可以在函数被调用时执行一个任意的闭包。
  12. 如果你想模拟静态函数,你可以调用 shmock_class,这将为你提供与实例相同的 Shmock 语义(在合理的情况下)。这在你想部分模拟一个对象,保留一些原始行为,但模拟出静态/受保护的函数时特别有用,这些函数可能存在于你正在测试的方法所依赖的上下文中。
  13. 你还可以独立于模拟实例来模拟一个类。

版权和许可

版权 2014 Box, Inc. 保留所有权利。

本软件基于Apache许可证2.0版本(以下简称“许可证”)许可;除非遵守许可证规定,否则不得使用此文件。您可以在以下地址获取许可证副本:

https://apache.ac.cn/licenses/LICENSE-2.0

除非适用法律要求或书面同意,否则在许可证下分发的软件是以“现状”为基础分发的,不提供任何形式的明示或暗示保证。有关许可证的具体语言、权限和限制,请参阅许可证。