如何在phpunit功能测试中模拟symfony控制器的自动服务?

时间:2018-06-03 14:22:19

标签: symfony mocking phpunit autowired functional-testing

无问题的上下文

让我们说在Symfony 3.3中我们有一个默认的控制器,它描绘了“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;
}

假设我们通过自动装配将其注入控制器。我们希望控制器能够像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 );
    }
}

当然,我可以在浏览器中多次重新加载页面,并且每次都会看到新文档随时间变化的文本。

但这不是自动化测试应该世界化的方式。我们应该模拟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以便自动装配我的模拟服务?

1 个答案:

答案 0 :(得分:2)

简短的回答是你没有。

WebTestCase用于更高级别的测试,有时称为功能测试或集成测试。它们旨在使用实际服务或适当的测试替代方案,例如用于测试的SQLite数据库而不是MySQL或Paypal-sandbox而不是生产服务。如果你想测试一个服务并用mock或stub替换它的依赖关系,你应该编写一个单元测试。

如果您想用虚拟实现替换您的服务,例如一个总是根据输入返回相同的id或散列的,您可以替换config/services_test.yaml中容器配置中的别名,该别名将在您的应用程序使用测试应用程序环境时使用(默认情况下为WebTestCase) )。您也可以尝试在运行时更改容器,但由于Symfony编译容器然后将其冻结,即不允许对容器进行任何更改,因此可能会非常棘手,并且不是真的建议。

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