首页 文章

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

提问于
浏览
1

无问题的背景

让我们说在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;
}

假设我通过 autowiring 将其注入控制器 . 我们希望控制器能够像 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() );
    }
}

我已经测试从内核获取容器,从客户端获取容器,并在那里设置服务,它确实 not 工作:

$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 回答

  • 2

    简短的回答是你没有 .

    WebTestCase用于更高级别的测试,有时称为功能测试或集成测试 . 它们旨在使用实际服务或适当的测试替代方案,例如用于测试的SQLite数据库而不是MySQL或Paypal-sandbox而不是 生产环境 服务 . 如果要测试服务并将其依赖项替换为模拟或存根,则应该编写单元测试 .

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

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

相关问题