使用模拟服务进行Symfony 2功能测试

Chr*_*isC 21 phpunit dependency-injection symfony

我有一个控制器,我想为其创建功能测试.该控制器通过MyApiClient类向外部API发出HTTP请求.我需要模拟这个MyApiClient类,所以我可以测试我的控制器如何响应给定的响应(例如,如果MyApiClient类返回500响应它将做什么).

MyApiClient通过标准的PHPUnit mockbuilder 创建类的模拟版本没有问题:我遇到的问题是让我的控制器将此对象用于多个请求.

我目前正在测试中执行以下操作:

class ApplicationControllerTest extends WebTestCase
{

    public function testSomething()
    {
        $client = static::createClient();

        $apiClient = $this->getMockMyApiClient();

        $client->getContainer()->set('myapiclient', $apiClient);

        $client->request('GET', '/my/url/here');

        // Some assertions: Mocked API client returns 500 as expected.

        $client->request('GET', '/my/url/here');

        // Some assertions: Mocked API client is not used: Actual MyApiClient instance is being used instead.
    }

    protected function getMockMyApiClient()
    {
        $client = $this->getMockBuilder('Namespace\Of\MyApiClient')
            ->setMethods(array('doSomething'))
            ->getMock();

        $client->expects($this->any())
            ->method('doSomething')
            ->will($this->returnValue(500));

        return $apiClient;
    }
}
Run Code Online (Sandbox Code Playgroud)

似乎在第二个请求发生时正在重建容器,导致MyApiClient再次实例化.所述MyApiClient类被配置为经由一个注释的服务(使用JMS DI额外包),并通过一个注解注入控制器的特性.

如果可以的话,我会将每个请求分成自己的测试来解决这个问题,但不幸的是我不能:我需要通过GET操作向控制器发出请求,然后POST它带回来的表单.我想这样做有两个原因:

1)表单使用CSRF保护,因此如果我直接POST到表单而不使用爬虫提交它,表单将无法通过CSRF检查.

2)测试表单在提交时生成正确的POST请求是奖励.

有没有人对如何做到这一点有任何建议?

编辑:

这可以在以下单元测试中表示,该测试不依赖于我的任何代码,因此可能更清楚:

public function testAMockServiceCanBeAccessedByMultipleRequests()
{
    $client = static::createClient();

    // Set the container to contain an instance of stdClass at key 'testing123'.
    $keyName = 'testing123';
    $client->getContainer()->set($keyName, new \stdClass());

    // Check our object is still set on the container.
    $this->assertEquals('stdClass', get_class($client->getContainer()->get($keyName))); // Passes.

    $client->request('GET', '/any/url/');

    $this->assertEquals('stdClass', get_class($client->getContainer()->get($keyName))); // Passes.

    $client->request('GET', '/any/url/');

    $this->assertEquals('stdClass', get_class($client->getContainer()->get($keyName))); // Fails.
}
Run Code Online (Sandbox Code Playgroud)

即使我$client->getContainer()->set($keyName, new \stdClass());在第二次呼叫之前立即呼叫,此测试也会失败request()

gen*_*exp 8

我以为我会跳进去 Chrisc,我想你想要的是:

https://github.com/PolishSymfonyCommunity/SymfonyMockerContainer

我同意您的一般方法,在服务容器中将其配置为参数实际上不是一个好方法.整个想法是能够在单个测试运行期间动态模拟这个.


Sim*_*ity 8

当您调用时self::createClient(),您将获得Symfony2内核的引导实例.这意味着,解析并加载所有配置.当现在发送请求时,你让系统第一次完成它的工作,对吗?

在第一个请求之后,您可能想要检查发生了什么,因此,内核处于发送请求的状态,但它仍在运行.

如果您现在运行第二个请求,则Web架构要求内核重新启动,因为它已经运行了请求.当您第二次执行请求时,将在您的代码中执行此重新引导.

如果要在发送请求之前引导内核并对其进行修改(如您所愿),则必须关闭旧内核实例并引导新内核实例.

你可以通过重新运行来做到这一点self::createClient().现在你再次必须像第一次那样应用你的模拟.

这是您的第二个示例的修改代码:

public function testAMockServiceCanBeAccessedByMultipleRequests()
{
    $keyName = 'testing123';

    $client = static::createClient();
    $client->getContainer()->set($keyName, new \stdClass());

    // Check our object is still set on the container.
    $this->assertEquals('stdClass', get_class($client->getContainer()->get($keyName)));

    $client->request('GET', '/any/url/');

    $this->assertEquals('stdClass', get_class($client->getContainer()->get($keyName)));

    # addded these two lines here:
    $client = static::createClient();
    $client->getContainer()->set($keyName, new \stdClass());

    $client->request('GET', '/any/url/');

    $this->assertEquals('stdClass', get_class($client->getContainer()->get($keyName)));
}
Run Code Online (Sandbox Code Playgroud)

现在您可能想要创建一个单独的方法,为您模拟新鲜实例,因此您不必复制代码...

  • 发布具有csrf保护的表单时,这将无效,因为令牌将不匹配 (3认同)