symfony - 如何在 phpunit 功能测试中模拟 symfony Controller 的自动连接服务?

标签 symfony mocking phpunit autowired functional-testing

非问题上下文

假设在 Symfony 3.3 中,我们有一个默认的 Controller 来绘制“Hello, world!”:

class DefaultController extends Controller
{
    public function indexAction() : Response
    {
        return new Response( 'Hello, world!' );
    }
}

如果我想测试它,我只需创建一个 WebTestCase 并在客户端或爬虫上做一些断言,例如
class DefaultControllerTest extends WebTestCase
{
    public function testIndex()
    {
        $client = static::createClient();

        $crawler = $client->request( 'GET', '/route/to/hello-world/' );

        $this->assertEquals( 200, $client->getResponse()->getStatusCode() );
        $this->assertContains( 'Hello', $crawler->filter( 'body' )->text() );
    }
}

这很好用。

有问题的上下文

假设我们有一些经过单元测试的服务。例如 IdGenerator当我们需要时基于某种算法创建新 ID 的服务,它们只是纯文本:
class IdGenerator
{
    public function generateNewId() : string;
}

假设我们通过 注入(inject)它 Autowiring 进入 Controller 。我们预计 Controller 会说 Hello, world, on request 8bfcedbe1bf3aa44e0545375f0e52f6b969c50fb!这一堆字符来自IdGenerator。
class DefaultController extends Controller
{
    public function indexAction( IdGenerator $idGenerator ) : Response
    {
        $id = $idGenerator->generateNewId();
        $message = sprintf( 'Hello, world, on request %s!', $id );
        return new Response( $message );
    }
}

当然,我可以在浏览器中多次重新加载页面,并且每次都会看到文本随着时间的推移使用新的 ID 发生变化。

但这不是自动化测试应该采用的方式。我们应该模拟 IdGenerator,所以它返回一个特定的 id断言:
class DefaultControllerTest extends WebTestCase
{
    public function testIndex()
    {
        $id = 'test-id-test-id-test-id';
        $idGenerator = $this->createMock( IdGenerator::class );
        $idGenerator->method( 'generateNewId' )->willReturn( $id );

        // What do I have to do with $idGenerator now here????

        $client = static::createClient();

        // Do something else here?

        $crawler = $client->request( 'GET', '/admin/flights/search/' );

        $this->assertEquals( 200, $client->getResponse()->getStatusCode() );
        $this->assertContains( $id, $crawler->filter( 'body' )->text() );
    }
}

我已经测试过从内核、客户端获取容器,并在那里设置服务,它确实 不是 工作:
    $id = 'test-id-test-id-test-id';
    $idGenerator = $this->createMock( IdGenerator::class );
    $idGenerator->method( 'generateNewId' )->willReturn( $id );

    $client = static::createClient();
    $kernel = $client->getKernel();
    $container = $kernel->getContainer();
    $container->set( 'My\Nice\Project\Namespace\IdGenerator', $idGenerator );

它仍然得到自动接线的,而不是我想要的(模拟)。

问题

如何设置 WebTestCase 以便 Autowiring 连接我的模拟服务?

最佳答案

简短的回答是你没有。

WebTestCase 用于更高级别的测试,有时称为功能或集成测试。它们旨在使用实际服务或适当的测试替代方案,例如用于测试的 SQLite 数据库而不是 MySQL 或 Paypal 沙盒而不是生产服务。如果您想测试服务并用模拟或 stub 替换其依赖项,则应改为编写单元测试。

如果您想用虚拟实现替换您的服务,例如总是根据输入返回相同的 id 或散列,您可以替换 config/services_test.yaml 中容器配置中的别名只要您的应用程序使用测试应用程序环境(默认情况下 WebTestCase 会这样做),就会使用它。您也可以尝试在运行时更改容器,但是因为 Symfony 编译容器然后卡住它,即不允许对容器进行任何更改,这可能会很棘手并且不推荐。

作为进一步的引用,Symfony 4.1 提供了一个包含所有服务的容器,包括私有(private)服务,暴露:https://symfony.com/blog/new-in-symfony-4-1-simpler-service-testing
这可能对您的情况没有帮助,但它显示了您如何与 WebTestCase-tests 中的服务容器交互。

关于symfony - 如何在 phpunit 功能测试中模拟 symfony Controller 的自动连接服务?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50667378/

相关文章:

Symfony JWT - 使用 symfony lexik JWT 身份验证包更改登录方式

unit-testing - 什么是模拟?

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

php - 在 PHPUnit 中如何强制 tearDownAfterClass() 在意外异常的情况下运行

php - 我如何正确测试 Laravel 事件?

Symfony2 - github - 发布 packagist

php - Symfony 4 不 Autowiring 动态路由 Controller

php - 如何在 symfony2 中使用 doctrine findOneBy 方法返回数组而不是对象?

javascript - 如何模拟 readline.createInterface()?

PHPUnit: stub 多个接口(interface)