用于模拟由新类对象调用的方法的单元测试

Sum*_*itk 30 php phpunit mocking

我正在为现有代码编写单元测试,就像这样

class someClass {
    public function __construct() { ... }

    public function someFoo($var) {
        ...
        $var = "something";
        ...
        $model = new someClass();
        model->someOtherFoo($var);
    }

    public someOtherFoo($var){
         // some code which has to be mocked
    }
}
Run Code Online (Sandbox Code Playgroud)

我应该如何模拟对函数" someOtherFoo" 的调用,使其some code在内部不执行" " someOtherFoo

class someClassTest {
   public function someFoo() {
      $fixture = $this->getMock('someClass ', array('someOtherFoo'));
      $var = "something";
      ....
      // How to mock the call to someOtherFoo() here
   }

}
Run Code Online (Sandbox Code Playgroud)

是否可以模拟构造函数,以便它返回我自己构造的函数或变量?

谢谢

Dav*_*ess 30

无论你new XXX(...)在被测试的方法中,你都注定要失败.将实例化提取到一个新方法 - createSomeClass(...)同一个类.这允许您创建一个被测试类的部分模拟,它从new方法返回一个stubbed或mock值.

class someClass {
    public function someFoo($var) {
        $model = $this->createSomeClass();  // call method instead of using new
        model->someOtherFoo($var);
    }

    public function createSomeClass() {  // now you can mock this method in the test
        return new someClass();
    }

    public function someOtherFoo($var){
         // some code which has to be mocked
    }
}
Run Code Online (Sandbox Code Playgroud)

在测试中,createSomeClass()在您调用的实例中进行someFoo()模拟,并someOtherFoo()在从第一个模拟调用返回的实例中进行模拟.

function testSomeFoo() {
    // mock someOtherFoo() to ensure it gets the correct value for $arg
    $created = $this->getMock('someClass', array('someOtherFoo'));
    $created->expects($this->once())
            ->method('someOtherFoo')
            ->with('foo');

    // mock createSomeClass() to return the mock above
    $creator = $this->getMock('someClass', array('createSomeClass'));
    $creator->expects($this->once())
            ->method('createSomeClass')
            ->will($this->returnValue($created));

    // call someFoo() with the correct $arg
    $creator->someFoo('foo');
}
Run Code Online (Sandbox Code Playgroud)

请记住,因为实例正在创建同一个类的另一个实例,所以通常会涉及两个实例.如果它更清楚,你可以在这里使用相同的模拟实例.

function testSomeFoo() {
    $fixture = $this->getMock('someClass', array('createSomeClass', 'someOtherFoo'));

    // mock createSomeClass() to return the mock
    $fixture->expects($this->once())
            ->method('createSomeClass')
            ->will($this->returnValue($fixture));

    // mock someOtherFoo() to ensure it gets the correct value for $arg
    $fixture->expects($this->once())
            ->method('someOtherFoo')
            ->with('foo');

    // call someFoo() with the correct $arg
    $fixture->someFoo('foo');
}
Run Code Online (Sandbox Code Playgroud)

  • 这种方法有一个问题。实际上 _createSomeClass()_ 不应该是公共的,因为它与类的接口无关,并且它公开了实现的内部细节。换句话说,传递 _new someClass()_ 会是一个更好的解决方案,但对于某些类别来说,它看起来也很糟糕(例如,对于 MVC 中的控制器来说,模型诞生时应该是底层)。 (2认同)

Jef*_*ett 6

您可以为模拟类名称添加前缀 overload:

查看关于Mocking Hard Dependencies的文档.

你的例子是这样的:

/**
 * @runTestsInSeparateProcesses
 * @preserveGlobalState disabled
 */
class SomeClassTest extends \PHPUnit\Framework\TestCase
{
    public function test_some_foo()
    {
        $someOtherClassMock = \Mockery::mock('overload:SomeOtherClass');
        $someOtherClassMock->shouldReceive('someOtherFoo')
            ->once()
            ->with('something')
            ->andReturn();

        $systemUnderTest = new SomeClass();

        $systemUnderTest->someFoo('something');
    }

}
Run Code Online (Sandbox Code Playgroud)

  • 我觉得这是更正确的答案 (2认同)
  • 有什么办法可以用 PHP Unit 本身而不是使用 Mockery 来创建模拟? (2认同)