用存根替换所有 class 个实例

Replace all class instances with stub

我正在测试一个 class,我们称它为 ClassUnderTest,使用另一个 class,我们称它为 OtherClass。在我的测试中我这样做:

$OtherClassStub = $this->createStub(OtherClass::class);
$OtherClassStub->method(...)
    ->willReturn(...);
$ClassUnderTest->otherClass = $OtherClassStub;

行得通。但是当 $ClassUnderTest 调用 new OtherClass() 时,会创建原始的 OtherClass class 而不是存根。

如何实现测试上下文中 OtherClass 的每个可能实例都被存根替换?

Perhaps 此处提供了您的问题的解决方案: How to Build a PHP Plugin Module System

这是一种将 classes 作为插件加载的方法,它们可以相互调用。通过 mod 稍微改进这个系统,您可以根据自己的代码创建任意数量的“new OtherClass()”,并且仍然可以访问其他 classes 的所有内容。如果你想要一个 class 的多个实例,perhaps mod将它变成这个方向:

  function load ($module,$instance) {
    if (isset($this->$module->$instance)) { return true; }

从上面link:

<?php
class Core {
  // (A) PROPERTIES
  public $error = ""; // LAST ERROR MESSAGE
  public $pdo = null; // DATABASE CONNECTION
  public $stmt = null; // SQL STATEMENT
  public $lastID = null; // LAST INSERT/UPDATE ID

  // (B) LOAD SPECIFIED MODULE
  //  $module : module to load
  function load ($module) {
    // (B1) CHECK IF MODULE IS ALREADY LOADED
    if (isset($this->$module)) { return true; }

    // (B2) EXTEND MODULE ON CORE OBJECT
    $file = PATH_LIB . "LIB-$module.php";
    if (file_exists($file)) {
      require $file;
      $this->$module = new $module();
      // EVIL POINTER - ALLOW OBJECTS TO ACCESS EACH OTHER
      $this->$module->core =& $this;
      $this->$module->error =& $this->error;
      $this->$module->pdo =& $this->pdo;
      $this->$module->stmt =& $this->stmt;
      return true;
    } else {
      $this->error = "$file not found!";
      return false;
    }
  }
}

ps。感谢 mod,是他让我付出了更多努力,让这个答案在线。现在答案好多了。

根据你的描述我推断原则上你有这样的事情:

class OtherClass {
    protected function someMethod(): bool
    {
        // determine $x ...
        return $x;
    }
}

class ClassUnderTest {
    public OtherClass $otherClass;

    public function methodToBeTested(): bool
    {
        $otherClass = new OtherClass();
        return $otherClass->someMethod();
    }
}

class ClassUnderTestTest extends TestCase {
    public function testMethodToBeTested(): void
    {
        $otherClassStub = $this->createStub(OtherClass::class);
        $otherClassStub->method('someMethod')
            ->willReturn(true);
        $classUnderTest = new ClassUnderTest();
        $classUnderTest->otherClass = $otherClassStub;
        
        $result = $classUnderTest->methodToBeTested();
        $this->assertTrue($result);
    }
}

现在您测试中的断言可能成立,也可能失败。为什么?因为您没有调用在 $otherClassStub 上存根的方法。相反,你在你正在测试的方法中实例化一个新的 $otherClass 对象(或某处)。

您的 ClassUnderTest 应该始终使用 ClassUndertTest::otherClass 属性中的 OtherClass 对象(假设这就是您首先将其放在那里的原因)。

或者您可以使用其他形式的依赖注入,例如通过使用像 Symfony 或 Laravel 这样的框架。 (在 Symfony 的情况下,你甚至可以使用 onlyDependencyInjection Component,不知道 Laravel 是否也可以。)

您实际问题的简单答案是:您无法更改 new 关键字的行为。在 class will always instantiate a new object based on exactly that class 上调用 new,除非 class 的构造函数定义了其他内容。

(您可能想直接了解 classes 和对象的概念,您的代码示例以及您的问题似乎表明您对此不太清楚。也许阅读一下以及 concept of dependency injection 上的内容会对您有所帮助。)