控制器的单元测试(Symfony)

Posted

技术标签:

【中文标题】控制器的单元测试(Symfony)【英文标题】:Unit Tests for Controller (Symfony) 【发布时间】:2018-08-03 15:21:28 【问题描述】:

如何为此控制器创建单元测试?我知道如何实现功能,但对单元测试一无所知...

class CatalogController extends Controller

    /**
     * @param Request $request
     * @return View
     */
    public function getAllAction(Request $request)
    
        $name = $request->query->get('name');
        $result = $this->getDoctrine()->getRepository('AppBundle:Category')->findBy(array('name' => $name));
        if ($result === NULL) 
            return new View("Catalog not found", Response::HTTP_NOT_FOUND);
        
        return new View($result,Response::HTTP_OK);
    
    /**
     * @param $id
     * @return View|object
     */
    public function getAction($id)
    
        $result = $this->getDoctrine()->getRepository('AppBundle:Category')->find($id);
        if (!$result instanceof Category) 
            return new View("ID: " . $id . " not found", Response::HTTP_NOT_FOUND);
        
        return new View($result, Response::HTTP_OK);
    
    /**
     * @param Request $request
     * @return View|Response
     */
    public function postAction(Request $request)
    
        $serializer = $this->get('jms_serializer');
        $content = $request->getContent();
        $category = $serializer->deserialize($content,'AppBundle\Entity\Category','json');
        $errors = $this->get('validator')->validate($category);
        if (count($errors) > 0) 
            return new View("NAME LENGTH MUST BE >4",Response::HTTP_BAD_REQUEST);
         else 
            $em = $this->getDoctrine()->getManager();
            $em->persist($category);
            $em->flush();
            return new View($category, Response::HTTP_OK);
        

.................................................. ..................................................... ..................................................... .....

【问题讨论】:

为什么不在 1 次测试中测试所有控制器?无需编写任何测试,只需设置路由器并运行即可,感谢:github.com/shopsys/http-smoke-testing 【参考方案1】:

有一个古老的笑话以“你如何从大象身上下来?”开头。并以“你没有,你从鸭子身上下来”结束。还是让我心碎。

关键是,如果您保持控制器动作纤细,那么您可能根本不需要对它们进行单元测试。当然,100% 代码覆盖率执行者会不同意这一点。

但是,如果您决心测试它们,那么您将需要进行一些认真的重构以保持您自己的理智。让我们看看你的 getAllAction:

public function getAllAction(Request $request)

    $name = $request->query->get('name');

因此您需要模拟一个请求对象,然后模拟一个袋子对象,然后添加一个测试以查看是否使用名称参数调用 get。充其量是痛苦的。然而,Symfony 实际上可以自动注入请求参数,所以:

public function getAllAction(string $name)

就是你所需要的。更少的代码。易于测试。有什么不喜欢的?

    $result = $this->getDoctrine()->getRepository('AppBundle:Category')->findBy(array('name' => $name));

现在这可能是一个真正的问题。如果您查看 getDoctrine 代码,您会发现它需要一个容器,该容器包含一个学说实体管理器注册表类,该注册表类又包含一个实体管理器,该实体管理器然后保存存储库。你真的想模拟所有这些对象并将它们串在一起吗?您将花费比实际代码更多的时间来调试您的测试。作为奖励,您的代码实际上不再适用于已远离服务定位器模式的 SF4。

幸运的是,使用动作注入很容易修复:

public function getAllAction(string $name, CategoryRepository $categoryRepository)

您必须做一些研究才能了解如何将您的存储库定义为服务,但测试起来并不难且相当容易。我们又一次摆脱了一段相当讨厌的代码。

现在这有点有趣:

return new View("Catalog not found", Response::HTTP_NOT_FOUND);

对于单元测试,我们当然对测试 View 类本身没有兴趣。相反,我们只想知道 View 是否是使用正确的参数构造的。拦截新操作没有简单的方法。

相反,我们可以定义并注入一个 ViewFactory

class ViewFactory
    public create($data,$status)
        return new View($data,$status)

所以现在很容易模拟视图工厂并测试 create 方法。作为奖励,控制器代码与 View 类没有太多关联。

因此,如果您真的觉得需要对这些动作进行单元测试,那么请卷起袖子开始重构。我可能会补充一点,看看 Symfony 中新的自动连线功能也是一个好主意。

【讨论】:

【参考方案2】:

如果你想通过单元测试单独测试一个 php 类,你应该注入你在类中使用的所有服务。

作为起点:

    /** @test */
public function it_should_return_a_view()

    $controller = new CatalogController();

    $result = $controller->getAllAction($mockedRequest, $mockedRepository);

    $this->assertInstanceOf(View::class, $result);

您需要模拟请求和存储库,这只有在您将其注入方法或通过类的构造函数注入时才有可能。

我希望这对您的单元测试方式有所帮助。

【讨论】:

以上是关于控制器的单元测试(Symfony)的主要内容,如果未能解决你的问题,请参考以下文章

单元测试 Symfony2

通过单元测试访问 Symfony 2 容器?

Symfony 单元测试和过多的内存泄漏?

对自定义 symfony 约束进行单元测试

Symfony4中的单元测试接口测试

如何使用 Symfony 2 在单元测试中获取当前登录的用户?