doulun1939
2017-06-05 11:04
浏览 13
已采纳

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

I know that creating an instance of a Class in the middle of a method it's a bad practice since it makes code hard to test. But I can't refactor the code, so I need to find a way to mock an Object created with new in the middle of a method under test.

Used Frameworks: PHPUnit, Mockery, WP_Mock

Example: Here I need to mock the get_second_string() method from the instance of the class ExternalClass

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');
    }
}

图片转代码服务由CSDN问答提供 功能建议

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

使用的框架: PHPUnit Mockery WP_Mock

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

 类MyClass {
 
函数methodUnderTest($ string){
 $ objToMock = new ExternalClass(); 
 $ second_string = $ objToMock  - > get_second_string(); 
 $ final_string = $ string。  $ second_string; 
返回$ final_string; 
} 
} 
Class TestMyClass扩展PHPUnit_Framework_TestCase {
公共函数setUp(){
} 
公共函数tearDown(){
} 
 
公共函数test_methodUnderTest  (){
 $ externalObject = $ this-> getMockBuilder('ExternalClass')
  - > setMethods(['get_second_string'])
  - > getMock; 
 $ externalObject-> expect($ this  - > once())
  - >方法('get_second_string')
  - > willReturn('two'); 
 $ testObj = new MyClass(); 
 $ this-> assertEquals(' 一两个',$ testObj-> methodUnderTest('one'); 
} 
} 
   
 
  • 点赞
  • 写回答
  • 关注问题
  • 收藏
  • 邀请回答

3条回答 默认 最新

  • dongmei8760 2017-06-07 08:01
    已采纳

    If you really have no opportunity to refactor the code or do some appropriate integration testing, you might want to take a look at https://github.com/php-test-helpers/php-test-helpers#intercepting-object-creation and https://github.com/krakjoe/uopz/tree/PHP5

    Still I think the code you make would profit a lot more from refactoring than monkey patching.

    Besides, the refactoring does not need to be very heavy. You might do at least this:

    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-'));
        }
    }
    
    点赞 打赏 评论
  • dtds8802 2017-06-05 11:08

    IMHO there is no way to do such a thing. You should pass the object as parameter to the method.

    点赞 打赏 评论
  • duanliaoyu8419 2017-06-05 11:37

    You can not mock entire object. But with phpunit you can do something like this:

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

    This way, newly created object ommits constructor and you specify which method are going to behave normally and which you mock.

    in testing you can specify what the method/s will return, like this:

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

    using this, you will not test the mocked object in any way, but can test method which is creating the object..

    点赞 打赏 评论

相关推荐 更多相似问题