PHPUnit中的Mock Composer实际上正在运行。

3

我正在构建一个需要直接访问Composer的PHP应用程序。为了测试该应用程序,我不想实际运行composer,因此我试图模拟它。

<?php

namespace MyNamespace;

use Composer\Console\Application;
use Symfony\Component\Console\Input\ArrayInput;

class MyTest extends \PHPUnit_Framework_TestCase {

    public function testComposer()
    {
        /** @var Application|\PHPUnit_Framework_MockObject_MockObject $composer */
        $composer = $this->getMockForAbstractClass(Application::class);
        $composer
            ->expects($this->any())
            ->method('run')
            ->will($this->returnValue(true));

        $this->assertTrue(
            $composer->run(new ArrayInput(['command' => 'install']))
        );
    }

}

这实际上运行了代码:

$ bin/phpunit -c phpunit-fast.xml tests/MyTest.php
PHPUnit 4.8.10 by Sebastian Bergmann and contributors.

Runtime:        PHP 5.6.13-1+deb.sury.org~trusty+3 with Xdebug 2.3.2
Configuration:  phpunit-fast.xml

Loading composer repositories with package information
Installing dependencies (including require-dev) from lock file
Nothing to install or update
Generating autoload files

我尝试了各种->getMock的组合(完全失败),以及->getMockBuilder,但它总是似乎实际上使用->run方法而不是存根。 我假设它在自身内部以某种方式替换了这些方法,但如果是这种情况,我该如何防止它呢?
更新
有人问为什么我使用getMockForAbstractClass而不只是getMock。 当使用getMock时,我会得到以下输出:
PHPUnit 4.8.10 by Sebastian Bergmann and contributors.

Runtime:        PHP 5.6.13-1+deb.sury.org~trusty+3 with Xdebug 2.3.2
Configuration:  phpunit.xml.dist

E

Time: 1.19 minutes, Memory: 4.50Mb

There was 1 error:

1) MyNamespace\MyTest::testComposer
PHPUnit_Framework_MockObject_RuntimeException: Cannot mock Symfony\Component\Console\Application::setDispatcher() because a class or interface used in the signature is not loaded

tests/MyTest.php:22

Caused by
ReflectionException: Class Symfony\Component\EventDispatcher\EventDispatcherInterface does not exist

tests/MyTest.php:22

FAILURES!
Tests: 1, Assertions: 0, Errors: 1.

尽管只使用$composer = new Application();的方法可以正常工作,但事实上,如果我在测试代码之前添加这一行,它仍然声称未加载类或接口,尽管对象早期已经正确实例化。


Application 类作曲家不是一个抽象类。那你为什么要使用 getMockForAbstractClass() 呢? - Sven
好问题,我会更新上面的信息以解释原因。 :) - DanielM
当我这样做时,它对我有效:$composer = $this->getMock('\Composer\Console\Application');显示Application::class时,你得到了什么? - Bang
尽量避免使用字符串来识别类。::class 是正确的,也更加优雅。现在的问题是:你为什么缺少那个接口?你是否查看了安装的依赖项并进行了搜索?它是否存在?我获取模拟对象的常规步骤是 $this->getMockBuilder(Any::class)->disableOriginalConstructor()->getMock(),因为获取模拟对象不应该执行任何操作。这很繁琐,这就是我更喜欢使用 Mockery 或 Prophecy 的原因。 - Sven
是的,问题似乎在于composer需要“symfony/console”:“〜2.5”,这需要开发人员的“symfony/event-dispatcher”:“〜2.1”。安装所有dev依赖项将解决此问题,将“symfony/event-dispatcher”:“〜2.1”添加到我的依赖项中将解决该问题。然而,这也似乎不是我的问题要解决。我没有在那里测试这些组件,因此应该能够将它们模拟出来。:/我想我会在他们的github上打开一个问题,看看他们会说什么。 - DanielM
显示剩余2条评论
2个回答

1

我有三种解决方法:

1. 自己添加事件调度器

将 "symfony/event-dispatcher" 添加到您自己的 require-dev 中。

"require-dev" : {
    ...
    "symfony/event-dispatcher" : "^2.1"
}

使用更正后的测试:
<?php
/**
 * MyTest.php
 */

namespace MyNamespace;

use Composer\Console\Application;
use Symfony\Component\Console\Input\ArrayInput;

class MyTest extends \PHPUnit_Framework_TestCase {

    public function testComposer()
    {
        /** @var Application|\PHPUnit_Framework_MockObject_MockObject $composer */
        $composer = $this->getMock(Application::class);
        $composer
            ->expects($this->any())
            ->method('run')
            ->will($this->returnValue(true));

        $this->assertTrue(
            $composer->run(new ArrayInput(['command' => 'install']))
        );
    }
}
个人注释: 这似乎是一种不太正规的方法,但是绝对是最简单的解决方案。

2. 使用Prophecy

使用Prophecy和PHPUnit一起模拟控制台。

"require-dev" : {
    ...
    "phpspec/prophecy": "~1.0"
}

现在测试看起来像这样:

<?php
/**
 * MyTest.php
 */

namespace MyNamespace;

use Composer\Console\Application;
use Prophecy\Prophet;
use Prophecy\Prophecy\ObjectProphecy;
use Symfony\Component\Console\Input\ArrayInput;

class MyTest extends \PHPUnit_Framework_TestCase {

    public function testComposer()
    {
        $prophet = new Prophet();
        $composerProphecy = $prophet->prophesize(Application::class);
        $composerProphecy
            ->run(new ArrayInput(['command' => 'install']))
            ->willReturn(true);

        /** @var Application $composer */
        $composer = $composerProphecy->reveal();

        $this->assertTrue(
            $composer->run(new ArrayInput(['command' => 'install']))
        );
    }
}

个人注记: 我不太喜欢使用魔术方法来调用方法,因为这会使我的 IDE 感到不安。

3. 使用 Mockery

另一个模拟系统的选项。

"require-dev" : {
    ...
    "mockery/mockery": "^0.9.4"
}

而且测试:

<?php
/**
 * MyTest.php
 */

namespace MyNamespace;

use Composer\Console\Application;
use Symfony\Component\Console\Input\ArrayInput;

class MyTest extends \PHPUnit_Framework_TestCase {

    public function testComposer()
    {
        /** @var Application|\Mockery\MockInterface $composer */
        $composer = \Mockery::mock(Application::class);
        $composer
            ->shouldReceive('run')
            ->with(ArrayInput::class)
            ->andReturn(true);


        $this->assertTrue(
            $composer->run(new ArrayInput(['command' => 'install']))
        );

        \Mockery::close();
    }
}

个人笔记:静态使用、需要记住清理和在shouldReceive中错误地使用可变参数使我非常难过。

4.(附加题)修复Symfony控制台

看起来不太可能,但如果有人能够解决#8200的问题,则意味着没有人需要使用多个模拟框架(如果您已经使用PHPUnit),也不需要为一个破损的测试添加脏的hack。


附注:我选择了选项1。 - DanielM

0

抱歉,我还不能发表评论。 :/

简而言之:getMock变体对我有效。检查你的环境。

通过composer获取composer(在composer.json中要求“composer / composer”:“@dev”)使测试(使用getMock)通过了。

检查你正在使用哪个版本的symfony(我的是2.7.4),以及你是否正在使用composer的phar版本(你可能不应该这样做)。


我已经尝试过dev-master和alpha10,但都没有成功。虽然我没有直接使用Symfony,但是我正在使用一些工具所需的一些包。 - DanielM
好的,就像Sven所说的那样,你应该尝试搜索已安装的依赖项以查找此接口。 或者,您可以将Symfony添加为直接依赖项,看看是否有任何区别。 - Michael D.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接