PHPUnit 测试 class 的特定方法与依赖关系

PHPUnit test specific method of class with dependencies

在阅读了很多文档之后,我想到了这个函数来测试服务方法findAllUsers。 我首先创建存储库的存根,然后告诉相关方法 findAllusers returns(是的,它们在存储库和服务中具有相同的名称),然后创建 class 的实例我想用 repo 存根和空实例测试 UserService.php

public function testFindAllUsers()
{
    $userProvider = new UserProvider();
    $users = $userProvider->getSampleUsers();

    $repoStub = $this->createMock(UserRepository::class);
    $repoStub->method('findAllUsers')->willReturn($users);

    // Here I want to instantiate UserService with my custom repo stub but then I have to add the other dependencies as well
    // They are only empty instances and they don't matter in this test case so it works but is that correct? What should / could I do else?
    $service = new UserService($repoStub, new UserValidation(new Logger(), $repoStub), new Logger());
    $this->assertEquals($users, $service->findAllUsers());
}// Test passes

classUserService.php

的构造函数
public function __construct(UserRepository $userRepository, UserValidation $userValidation,LoggerInterface $logger){ ... } 

UserValidation.php

public function __construct(LoggerInterface $logger, UserRepository $userRepository){ ... }

我觉得 class 不太适合向 class 提供空实例。有没有更清洁的方法? 我想我想要的是创建一个 UserService 的实例,只提供相关的依赖项(模拟的 UserRepository 实例),让其余的由 di 容器自动装配 php-di 这样做说得通? 我听说过创建一个容器来测试 returns 手动创建具有默认 return 值的假 classes 因为它无论如何用于以后的集成测试。 我很乐意看到一些示例和变体。

我对自动化测试还很陌生,所以欢迎任何对此功能的批评,即使它与问题没有直接关系。

编辑

阅读评论和答案后,我认为我能想象的理想功能是在容器中注册 UserService class ,该容器使用所需的自定义依赖项(在我的case UserRepository) 的 mock 和(为了最少的开销)其他依赖项的空 mock 并且不仅用自定义 mock 替换 repo 并让自动装配功能注入真正的依赖项。 但也许我在这里错了,我不知道注入真正的依赖项是否更慢或开销更大

尽管在容器中注册新依赖项的函数必须以某种方式检测给定参数(包含 UserRepository 或数组,如果有多个)和所需的缺失依赖项(UserValidationLogger) 并模拟它们,最后使用 cutom UserRepository 和模拟的 UserValidationLogger.

创建 UserService 的实例

这是我的编程和 PHP 知识有限的地方,因为我不知道一种干净的编程方法。可能我不是第一个提出这个问题的人,而且无论如何它都是非常反模式的,但是是的,我很想知道您对这个想法的看法以及为什么它可能是个坏主意以及如何以其他方式完成。

您可以创建一个模拟存储库并将其设置到容器中。其余的将通过自动装配解决。确保为每个测试重新加载您的容器。

例子

要将模拟实例设置到容器中,请将此方法添加到特征或基础中 class:

use PHPUnit\Framework\MockObject\MockObject;

// ...

protected function mock(string $class): MockObject
{
    $mock = $this->getMockBuilder($class)
        ->disableOriginalConstructor()
        ->getMock();

    $this->container->set($class, $mock);

    return $mock;
}

测试用法

public function testFindAllUsers()
{
    // Data provider
    $userProvider = new UserProvider();
    $users = $userProvider->getSampleUsers();

    // Mock the required repositories
    $this->mock(UserRepository::class)
      ->method('findAllUsers')
      ->willReturn($users);

    // Instantiate UserService
    $service = $this->container->get(UserService::class);

    // Compare the result
    $this->assertEquals($users, $service->findAllUsers());
}