【问题标题】:Mock an object created in the middle of a method模拟在方法中间创建的对象
【发布时间】:2017-11-06 03:28:53
【问题描述】:

我知道在method 中间创建Class 的实例是一种不好的做法,因为它会使代码难以测试。 但是我无法重构代码,所以我需要找到一种方法来模拟一个在被测method 中间使用new 创建的Object

使用的框架:PHPUnitMockeryWP_Mock

示例:这里我需要从ExternalClass类的实例中模拟get_second_string()方法

Class MyClass {

    function methodUnderTest($string) {
        $objToMock = new ExternalClass();
        $second_string = $objToMock->get_second_string();
        $final_string = $string . $second_string;
        return $final_string;
    }
}
Class TestMyClass extends PHPUnit_Framework_TestCase {
    public function setUp() {
    }
    public function tearDown() {
    }

    public function test_methodUnderTest() {
        $externalObject = $this->getMockBuilder('ExternalClass')
                               ->setMethods(['get_second_string'])
                               ->getMock;
        $externalObject->expects($this->once())
                       ->method('get_second_string')
                       ->willReturn(' two');
        $testObj = new MyClass();
        $this->assertEquals('one two', $testObj->methodUnderTest('one');
    }
}

【问题讨论】:

  • 不重构代码,你就不走运了。
  • @MagnusEriksson,您认为重构代码的最佳方式是什么?我看过xmike的回答,但看起来不是很清楚

标签: php testing phpunit mockery


【解决方案1】:

如果你真的没有机会重构代码或做一些适当的集成测试,你可能想看看https://github.com/php-test-helpers/php-test-helpers#intercepting-object-creationhttps://github.com/krakjoe/uopz/tree/PHP5

我仍然认为你编写的代码从重构中获得的收益比猴子修补要多得多。

此外,重构不需要非常繁重。你至少可以这样做:

class MyClass
{
    private $externalsFactory;

    public function __construct($externalsFactory){
        $this->externalsFactory = $externalsFactory;
    }

    public function methodUnderTest($str){
        $external = $this->externalsFactory->make();
        $second_string = $external->get_second_string();
        $finalString = $str.$second_string;
        return $finalString;
    }
}

class ExternalsFactory
{
    public function make(){
        return new ExternalClass();
    }
}

class ExternalClass
{
    public function get_second_string(){
        return 'some real stuff may be even from database or whatever else it could be';
    }
}

class MyClassTest extends PHPUnit_Framework_TestCase
{
    private $factoryMock;
    private $myClass;

    public function setUp(){
        $this->factoryMock = $this->getMockBuilder('ExternalsFactory')
                                  ->getMock();
        $this->myClass = new MyClass($this->factoryMock);
    }

    public function testMethodUnderTest(){
        $extenalMock = $this->createMock('ExternalClass');
        $extenalMock->method('get_second_string')
                    ->willReturn('second');
        $this->factoryMock->method('make')
                          ->willReturn($extenalMock);
        $this->assertSame('first-and-second', $this->myClass->methodUnderTest('first-and-'));
    }
}

【讨论】:

  • 谢谢你的提示,我想我会通过这两个组件,因为它们已经老了。另一方面,您建议的重构对我来说不是很清楚。我将提供一个更广泛的示例,因此您应该更容易指出所需的更改
  • 我已经根据问题的更新更新了示例。我包含了工厂的虚拟类和ExternalClass(如您所愿)以使其完全可运行。
  • 非常感谢,想知道创建新类 ExternalsFactory 是否太多
【解决方案2】:

恕我直言,没有办法做这样的事情。您应该将对象作为参数传递给方法。

【讨论】:

    【解决方案3】:

    您不能模拟整个对象。 但是使用 phpunit 你可以这样做:

     $f = $this->getMockBuilder(<your_class>)->disableOriginalConstructor()
          ->setMethods(array(
              <mocked_method_1>, <mocked_method_2>
           ))->getMock();
    

    这样,新创建的对象会省略构造函数,您可以指定哪个方法将正常运行以及模拟哪个方法。

    在测试中,您可以指定方法将返回的内容,如下所示:

    $f->method(<mocked_method_1>)->willReturn(<dummy_data>);
    

    使用它,您将不会以任何方式测试模拟对象,但可以测试创建对象的方法..

    【讨论】:

    • 我知道如何模拟一个对象,我需要的是:
      function methodUnderTest() { $objToMock = new someClass(); echo $objToMock-&gt;method(); } 没有机会模拟someClass 的实例,我无法测试methodUnderTest ,原因很明显。
    • 那么您可以进行集成测试(您不需要模拟对象)或创建扩展测试的新类,复制代码并从工厂获取对象(可能很容易模拟) .但是第二种方法是有风险的,当原始代码更改时,您也必须更改测试代码.. 没有办法在测试方法中模拟新创建的类实例
    猜你喜欢
    • 1970-01-01
    • 2017-01-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-02-10
    • 2020-03-26
    • 2017-04-07
    • 2014-12-06
    相关资源
    最近更新 更多