模拟在方法中间创建的对象

Posted

技术标签:

【中文标题】模拟在方法中间创建的对象【英文标题】: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的回答,但看起来不是很清楚 【参考方案1】:

如果你真的没有机会重构代码或做一些适当的集成测试,你可能想看看https://github.com/php-test-helpers/php-test-helpers#intercepting-object-creation和https://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 ,原因很明显。 那么您可以进行集成测试(您不需要模拟对象)或创建扩展测试的新类,复制代码并从工厂获取对象(可能很容易模拟) .但是第二种方法是有风险的,当原始代码更改时,您也必须更改测试代码.. 没有办法在测试方法中模拟新创建的类实例

以上是关于模拟在方法中间创建的对象的主要内容,如果未能解决你的问题,请参考以下文章

无法从请求对象模拟用户访问令牌并测试方法

PHPUnit 模拟对象和方法类型提示

在此模拟对象上找不到模拟方法 shouldRecieve() [关闭]

设计模式代理模式 ( 动态代理 | 模拟 Java 虚拟机生成对应的 代理对象 类 )

是否可以使用 EasyMock 创建一个实现多个接口的模拟对象?

简单模拟jQuery创建对象的方法,以及封装一个js动画框架