Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/php/246.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/unit-testing/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
如何在PHP单元测试中模拟构造函数中调用的方法?_Php_Unit Testing_Mocking_Phpunit_Mockery - Fatal编程技术网

如何在PHP单元测试中模拟构造函数中调用的方法?

如何在PHP单元测试中模拟构造函数中调用的方法?,php,unit-testing,mocking,phpunit,mockery,Php,Unit Testing,Mocking,Phpunit,Mockery,我在单元测试一个在构造函数中调用了方法的类时遇到了问题。我不明白怎么嘲笑这个。也许我应该使用phpUnit的“设置”方法 我在用模拟图书馆。有比这更好的工具吗 如果您有任何建议,我们将不胜感激。只要扩展这个类并重写您的方法(如果它是公共的或受保护的)。这里的问题是,该方法无法模拟,因为对象尚未实例化。 sectus的答案是有效的,但可能不是很灵活,因为在不同的测试中很难改变模拟方法的行为 您可以创建另一个与要模拟的方法相同的类,并将该类的实例作为构造函数参数传递。这样,您就可以通过模拟类的测试

我在单元测试一个在构造函数中调用了方法的类时遇到了问题。我不明白怎么嘲笑这个。也许我应该使用phpUnit的“设置”方法

我在用模拟图书馆。有比这更好的工具吗


如果您有任何建议,我们将不胜感激。

只要扩展这个类并重写您的方法(如果它是公共的或受保护的)。

这里的问题是,该方法无法模拟,因为对象尚未实例化。 sectus的答案是有效的,但可能不是很灵活,因为在不同的测试中很难改变模拟方法的行为


您可以创建另一个与要模拟的方法相同的类,并将该类的实例作为构造函数参数传递。这样,您就可以通过模拟类的测试。通常,您遇到的问题是一个类做了太多事情的气味。

要测试这个类,您需要模拟内部对象(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;
    }
}
测试:


如果您的类很难实例化以进行测试,那么这是一种代码气味,表明您的类在构造函数中做了太多或太多的工作

警告标志

  • 构造函数或at字段声明中的新关键字
  • 在构造函数或at字段声明中调用静态方法
  • 构造函数中的字段分配以外的任何内容
  • 构造函数完成后对象未完全初始化(请注意 输出(用于初始化方法)
  • 构造函数中的控制流(条件逻辑或循环逻辑)
  • 代码在构造函数中进行复杂的对象图构造 而不是使用工厂或建筑商
  • 添加或使用初始化块
无论您的
methodToMock
函数在构造函数中做什么,都需要重新思考。正如其他答案中提到的,您可能希望使用依赖项注入来传递类正在做的事情


重新思考您的类实际在做什么,并进行重构,以便更易于测试。这还有一个好处,就是使您的类更容易在以后重用和修改。

我也想知道这就是我发现您的问题的原因。最后我决定做一些有点脏的事。。。使用反射

下面是我要测试的方法:

/**
 * 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;医生:

  • 解散
    \u构造
  • 设置模拟
  • 在实例化对象后,使用反射调用
    \uuu construct
  • 尽量不要为此而失眠

  • 谢谢你的回答!非常感谢,你能举个简单的例子吗?谢谢:)解释得很清楚!谢谢我会试试的,我会告诉你是怎么回事!然后被接受的答案将是:)没问题,希望它能帮助你。
    /**
     * @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
        ]);
    }
    
    /**
     * 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());
        };
    }
    
    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());
        }
    }