如何对具有依赖项的工厂进行单元测试

Posted

技术标签:

【中文标题】如何对具有依赖项的工厂进行单元测试【英文标题】:How to Unittest a Factory with dependencies 【发布时间】:2017-09-03 17:39:10 【问题描述】:

如何使用实体经理测试我的工厂?我有一个错误,因为我需要让我的容器返回一个从 doctrine 创建的类的实例(我什至不知道返回了什么)。

如何创建可以通过的测试?

// factory i want to test
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)

    $googleAppOption = $container->get(GoogleAppOptions::class);
    $em = $container->get('doctrine.entity_manager.orm_default');
    return new GoogleTokenHandler($googleAppOption, new GoogleTokenClient(), $em);


//test function
 public function testReturnsTokenHandlerInstance()

    $googleOptionsFactory = new GoogleOptionsFactory();
    $googleOptions = $googleOptionsFactory($this->container->reveal(), null);
    $this->container->get(GoogleAppOptions::class)->willReturn($googleOptions);
    $googleTokenHandlerFactory = new GoogleTokenHandlerFactory($this->container);
    $tokenHandler = $googleTokenHandlerFactory($this->container->reveal(), null);
    $this->assertInstanceof(GoogleTokenHandler::class, $tokenHandler);


【问题讨论】:

【参考方案1】:

这很难测试的事实是一个很好的迹象,表明这有什么不好的地方。在您的情况下,很明显容器被注入,然后被用于定位要处理的服务。我建议重写这个类来注入 OptionsFactory(或者更好的是选项)和 EntityManager 以及在构造函数中动态创建的 GoogleClient。你会得到一个看起来像这样的调用:

return new GoogleTokenHandler(
    $this->optionsFactory,
    $this->tokenClient,
    $this->entityManager
);

如您所见,您既没有使用$requestedName,也没有使用传递给__invoke 的可选$options。这有点奇怪,但这不会打扰我们的测试。现在您可以简单地模拟测试中的服务并检查调用是否返回正确的实例:

public function testFactoryInvokeReturnsInstance()

    $optionsFactory = $this->prophesize(OptionsFactory::class);
    $tokenClient = $this->prophesize(GoogleTokenClient::class);
    $entityManager = $this->prophesize(EntityManager::class);

    $factory = new MyFactory(
        $optionsFactory->reveal(),
        $tokenClient->reveal(),
        $entityManager->reveal()
    );

    $this->assertInstanceOf(GoogleTokenHandler::class, $factory->__invoke());
    // Alternatively you can use the __invoke-magic directly:
    $this->assertInstanceOf(GoogleTokenHandler::class, $factory());

你可以对你的类做同样的事情,但基本上你必须添加一个容器,然后为从中获取的所有服务存根 get-method。例如,您在 sn-p 中缺少实体管理器。如果在您的方法中创建的 GoogleTokenClient 需要一些参数/选项,则无法模拟该行为,实际上您将无法在不更改代码的情况下将其切换出去。而通过在构造函数中注入它,您可以重新配置您的容器以传入不同的对象。

对于后代,您的完整工厂可能看起来像这样:

class Factory 
    private $optionsFactory;
    private $tokenClient;
    private $entityManager;

    public function __construct(GoogleTokenClient $tokenClient, ...)
    
        $this->tokenClient = $tokenClient;
        ...
    

    public function __invoke()  return new GoogleTokenHandler(...); 

【讨论】:

但这意味着我需要一个工厂来创建我的工厂? 或者我应该在我的控制器中使用服务定位器来创建我的 TokenHanlderFactory 吗? 我不知道哪个工厂会创建一个工厂,但一般来说 - 如果涉及一些动态 - 拥有“FactoryFactory”可能很有用,并且在 Java 代码库中它有点常见(当在请求之间保留一些状态)。在您的控制器中,您可以使用容器来获取工厂或使用一些 DI 容器直接获取 TokenHandler,例如使用 Symfony 的容器:symfony.com/doc/current/service_container/factories.html 根据您的严格程度,您甚至可以将使用过的服务注入控制器并像常规服务一样使用它。如果没有,控制器几乎是唯一合法应该有权访问容器的东西。即使这样,他们也应该尽量少做,以免控制器做太多事情

以上是关于如何对具有依赖项的工厂进行单元测试的主要内容,如果未能解决你的问题,请参考以下文章

为啥使用 jasmine 对 Node typescript 项目进行 Karma 单元测试会显示包含依赖项的覆盖范围?

单元测试-桩对象

如何对依赖于 ActivatedRoute 参数的组件进行单元测试?

如何在 python 中对装饰器工厂输入进行单元测试

C#单元测试--如何使用moq.mock进行依赖注入

如何对 AngularJS $promise.then 进行单元测试