PHPUnit 中的 Mock Composer 实际上是 运行

Mock Composer in PHPUnit is actually running

我正在构建一个需要直接访问 Composer 的 PHP 应用程序。然而,为了测试应用程序,我实际上并不想 运行 作曲家,所以我试图模拟它。

<?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(); 就可以正常工作。事实上,如果我在测试上方添加该行,它 仍然 声称未加载 class or interface,尽管该对象之前已正确实例化。

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

TL;DR: getMock 变体对我有用。检查您的环境。

通过 composer 获取 composer(在 composer.json require "composer/composer": "@dev")使我通过了测试(使用 getMock)。

检查您使用的是哪个版本的 symfony(我的是 2.7.4)以及您是否使用了 composer 的 phar 版本(您可能不应该使用)。

我有 3 个解决方案:

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']))
        );
    }
}

个人说明:这感觉像是一个肮脏的 hack,但这是迄今为止最简单的解决方案

2。使用预言

将 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。使用嘲弄

模拟系统的另一种选择。

"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)或添加肮脏的 hacks他们的 require-dev 用于单个失败的测试。