我在使用构造函数中调用的方法对类进行单元测试时遇到了问题。我不明白如何嘲笑这个。也许我应该使用' setUp' phpUnit的方法?
我正在使用Mockery库。是否有比这更好的工具?
class ToTest
{
function __construct() {
$this->methodToMock(); // need to mock that for future tests
}
// my methods class
}
任何建议都将不胜感激。
答案 0 :(得分:5)
如果你的类很难实例化来测试,那就是你的类做得太多或在构造函数中工作的代码味道。
http://misko.hevery.com/code-reviewers-guide/
Flaw #1: Constructor does Real Work
警告标志
无论您的methodToMock
函数在构造函数中的作用是什么,都需要重新考虑。正如其他答案中所提到的,您可能希望使用依赖注入来传递您的类正在执行的操作。
重新思考你的班级实际上在做什么和重构,以便更容易测试。这样做的好处是可以让您的课程在以后更容易重复使用和修改。
答案 1 :(得分:2)
这里的问题是该方法不能被模拟,因为该对象尚未实例化。 sectus答案是有效的,但可能不是很灵活,因为在不同的测试中改变模拟方法的行为可能很困难。
您可以创建另一个与您要模拟的方法相同的类,并将该类的实例作为构造函数参数传递。这样你就可以在测试中传递一个模拟类。通常你遇到的问题是上课的气味太多了。
答案 2 :(得分:2)
要测试此类,您将模拟内部对象(methodToMock),然后使用依赖注入来传递模拟服务而不是真实服务。
类别:
class ToTest{
private $svc;
// Constructor Injection, pass the Service object here
public function __construct($Service = NULL)
{
if(! is_null($Service) )
{
if($Service instanceof YourService)
{
$this->SetService($Service);
}
}
}
function SetService(YourService $Service)
{
$this->svc = $Service
}
function DoSomething($request) {
$svc = $this->svc;
$result = $svc->getResult($request); // Get Result from Real Service
return $result;
}
function DoSomethingElse($Input) {
// do stuff
return $Input;
}
}
测试:
class ServiceTest extends PHPUnit_Framework_TestCase
{
// Simple test for DoSomethingElse to work Properly
// Could also use dataProvider to send different returnValues, and then check with Asserts.
public function testDoSomethingElse()
{
$TestClass = new YourService();
$this->assertEquals(1, $TestClass->DoSomethingElse(1));
$this->assertEquals(2, $TestClass->DoSomethingElse(2));
}
public function testDoSomething()
{
// Create a mock for the YourService class,
// only mock the DoSomething() method. Calling DoSomethingElse() will not be processed
$MockService = $this->getMock('YourService', array('DoSomething'));
// Set up the expectation for the DoSomething() method
$MockService->expects($this->any())
->method('getResult')
->will($this->returnValue('One'));
// Create Test Object - Pass our Mock as the service
$TestClass = new ToTest($MockService);
// Or
// $TestClass = new ToTest();
// $TestClass->SetService($MockService);
// Test DoSomething
$RequestString = 'Some String since we did not specify it to the Mock'; // Could be checked with the Mock functions
$this->assertEquals('One', $TestClass->DoSomething($RequestString));
}
}
答案 3 :(得分:1)
我也想知道这就是我找到你问题的方式。最后我决定做一些有点脏的事情......用反射。
以下是我要测试的方法:
/**
* ArrayPool constructor.
* @param array $tasks Things that might be tasks
*/
public function __construct(array $tasks)
{
foreach ($tasks as $name => $parameters) {
if ($parameters instanceof TaskInterface) {
$this->addTask($parameters);
continue;
}
if ($parameters instanceof DescriptionInterface) {
$this->addTask(new Task($parameters));
continue;
}
$this->addPotentialTask($name, $parameters);
}
}
出于本次测试的目的,我不想实际运行->addTask
或->addPotentialTask
,只知道它们会被调用。
以下是测试:
/**
* @test
* @covers ::__construct
* @uses \Foundry\Masonry\Core\Task::__construct
*/
public function testConstruct()
{
$task = $this->getMockForAbstractClass(TaskInterface::class);
$description = $this->getMockForAbstractClass(DescriptionInterface::class);
$namedTask = 'someTask';
$parameters = [];
$arrayPool =
$this
->getMockBuilder(ArrayPool::class)
->disableOriginalConstructor()
->setMethods(['addTask', 'addPotentialTask'])
->getMock();
$arrayPool
->expects($this->at(0))
->method('addTask')
->with($task);
$arrayPool
->expects($this->at(1))
->method('addTask')
->with($this->isInstanceOf(TaskInterface::class));
$arrayPool
->expects($this->at(2))
->method('addPotentialTask')
->with($namedTask, $parameters);
$construct = $this->getObjectMethod($arrayPool, '__construct');
$construct([
0=>$task,
1=>$description,
$namedTask => $parameters
]);
}
魔术发生在getObjectMethod
中,它接受一个对象并返回一个可调用的闭包,该闭包将在该对象的一个实例上调用该方法:
/**
* Gets returns a proxy for any method of an object, regardless of scope
* @param object $object Any object
* @param string $methodName The name of the method you want to proxy
* @return \Closure
*/
protected function getObjectMethod($object, $methodName)
{
if (!is_object($object)) {
throw new \InvalidArgumentException('Can not get method of non object');
}
$reflectionMethod = new \ReflectionMethod($object, $methodName);
$reflectionMethod->setAccessible(true);
return function () use ($object, $reflectionMethod) {
return $reflectionMethod->invokeArgs($object, func_get_args());
};
}
我知道循环和条件都能正常运行而不需要进入代码我不想进入这里。
TL; DR:
__construct
__construct
答案 4 :(得分:0)
只需扩展此类并覆盖您公开或受保护的方法。
答案 5 :(得分:0)
class ToTest
{
function __construct(){
$this->methodToMock(); // need to mock that for future tests
}
// my methods class
public function methodToMock(){}
}
class ToTestTest{
/**
* @test
* it should do something
*/
public function it_should_do_something(){
$ToTest = \Mockery::mock('ToTest')
->shouldDeferMissing()
->shouldReceive("methodToMock")
->andReturn("someStub")
->getMock();
$this->assertEquals($expectation, $ToTest->methodToMock());
}
}