使用Doctrine在Symfony2中测试控制器

9

我已使用Symony2在REST控制器中创建了一个非常简单的控制器,其中包含数据库插入/更新/删除操作。

有没有一种好的方法可以为这些控制器操作编写单元/集成测试,而不会污染生产数据库?我是否必须使用不同的环境 - 或者框架供应商是否提出了一个建议的方法来解决这个问题?

当前控制器示例:

public function postAction()
{
    $json = $this->getRequest()->getContent();
    $params = json_decode($json);
    $name = $params->name;
    $description = $params->description;

    $sandbox = new Sandbox();
    $sandbox->setName($name);
    $sandbox->setDescription($description);
    $em = $this->getDoctrine()->getManager();
    $em->persist($sandbox);
    $em->flush();

    $response = new Response('/sandbox/'.$sandbox->getId());
    $response->setStatusCode(201);
    return $response;
}

当前测试示例:

class SandboxControllerTest extends WebTestCase
{

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

        $crawler = $client->request('POST', '/service/sandbox', array(), array(), array(), json_encode(array('name' => 'TestMe', 'description' => 'TestDesc')));

        $this->assertEquals(
                201, $client->getResponse()->getStatusCode()
        );
    }
}

2
你绝对不应该在生产数据库上运行单元测试。相反,你应该有一个测试数据库! - j0k
@j0k 这是真的!我不确定Symfony2中的开发和部署过程会是什么样子。我该如何告诉phpunit在Symfony2中使用哪个数据库环境...? - madflow
请查看文档,特别是末尾关于测试配置的部分。 - j0k
3个回答

18

我认为在测试中应该避免更改数据库。

我喜欢的方法是在测试客户端内注入实体管理器模拟对象。例如:

public function testRest()
{
    // create entity manager mock
    $entityManagerMock = $this->getMockBuilder('Doctrine\ORM\EntityManager')
        ->setMethods(array('persist', 'flush'))
        ->disableOriginalConstructor()
        ->getMock();

    // now you can get some assertions if you want, eg.:
    $entityManagerMock->expects($this->once())
        ->method('flush');

    // next you need inject your mocked em into client's service container
    $client = static::createClient();
    $client->getContainer()->set('doctrine.orm.default_entity_manager', $entityManagerMock);

    // then you just do testing as usual
    $crawler = $client->request('POST', '/service/sandbox', array(), array(), array(), json_encode(array('name' => 'TestMe', 'description' => 'TestDesc')));

    $this->assertEquals(
            201, $client->getResponse()->getStatusCode()
    );
}

需要注意的是,使用这个解决方案时,你需要在每个请求之前注入你的模拟服务。这是因为客户端在每个请求之间重新启动内核(这意味着容器也会被重建)。

编辑:

我的控制器测试中使用的 GET 方法是,我可以模拟实体仓储等内容以便在存根中获取数据库中的所有数据,但这是很费力的且不太舒适,所以在这种情况下(我指的只是控制器的测试)我更喜欢从数据库中实际获取真实数据。所谓真实数据,就是用Doctrine fixtures创建的数据。只要我们不改变数据库,我们就可以依赖它们。

但是,如果我们要更改数据库中的数据(POST/PUT/DELETE方法),我总是使用模拟。如果你使用em模拟,并在"persist"和"flush"方法上设置适当的期望,你可以确信数据被正确地创建/更新/删除,而且实际上没有进行任何数据库修改。


我喜欢!但是如果实际上没有表现形式,我该如何测试PUT / DELETE / GET请求呢?例如,GET / service / sandbox / 1应该获取ID为1的沙盒。在我的数据库思维中,我会先创建资源,然后进行GET / PUT和DELETE操作。 - madflow
好的 - 我必须阅读一些有关模拟对象的资料 - 但这可以完美地工作!谢谢! - madflow
我已经编辑了我的回答,希望这能带来更多的澄清。最好! :) - Cyprian
尝试了一下...非常好用!没想到你也可以通过编程方式set服务。我总是使用容器来get它们,并且只在配置文件和单元测试中建立它们(没有容器,只需将模拟对象传递给需要它的构造函数)。在功能测试中伪造内核的好技巧,肯定对许多其他控制器测试有用,比如防止发送邮件或防止访问远程服务。这个技巧大大简化了功能测试!!!谢谢@Cyprian - Xavi Montero
嘿,它之前是可以工作的,但是如果我添加了FOSUserBundle和HWIOAuthBundle,它现在会失败!!!它会显示“PHP致命错误:在/files/custom_www/antique-crayon/preproduction/vendor/doctrine/orm/lib/Doctrine/ORM/EntityManager.php的第759行上调用一个非对象的getRepository()成员函数”- 如果我停止注入“doctrine.orm.default_entity_manager”(当然要注释掉期望的一次),那么它就会给出绿色的进度条(当然,这时会保存到真正的数据库中,这是我们想要避免的)。 - Xavi Montero
显示剩余2条评论

4

这是我所做的:

在你的测试类中加入$kernel静态变量,并创建一个函数,在测试模式下加载内核。

protected static $kernel;
protected static $container;

public static function setUpBeforeClass()
{
    self::$kernel = new \AppKernel('test', true);
    self::$kernel->boot();

    self::$container = self::$kernel->getContainer();
}

作为测试函数的第一行,调用self:setUpBeforeClass()
这将使Symfony加载config_test.yml配置文件,并在其中定义不同的数据库连接。

1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接