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

2022-08-30 12:44:09

我正在为现有代码编写单元测试,如下所示

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
    }
}

在这里,我应该如何模拟对函数“”的调用,以便它不会在内部执行“”?someOtherFoosome codesomeOtherFoo

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

}

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

谢谢


答案 1

无论你在测试的方法中处于什么位置,你都注定要失败。将实例化提取到同一类的新方法----。这允许您创建受测类的部分模拟,该模拟从新方法返回存根或模拟值。new XXX(...)createSomeClass(...)

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
    }
}

在测试中,mock 在你调用的实例中,在从第一个模拟调用返回的实例中 mock。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');
}

请记住,由于该实例正在创建同一类的另一个实例,因此通常会涉及两个实例。您可以在此处使用相同的模拟实例,如果它使它更清晰。

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

答案 2

您可以在模拟类名前面加上前缀overload:

查看有关模拟硬依赖关系的文档

您的示例如下所示:

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

}

我添加了注释,因为通常模拟类也会在其他测试中使用。如果没有注释,则自动加载程序将由于错误而崩溃。@runTestsInSeparateProcessesclass already exists

如果这是测试套件中使用模拟类的唯一位置,则应删除注释。


推荐