模拟包装器时间函数

Mocking a wrapper time function

我在 class

中为 php time() 方法创建了一个小包装函数
class MyClass
{
    public function myFuncion($unixTimeStamp)
    {
        $pending = $this->isPending($unixTimeStamp)

        // data manipulation

        return $result
    }

    protected function isPending($unixTimeStamp)
    {
        if ($unixTimeStamp > $this->currentTime()) {
            return true;
        }

        return false;
    }

    public function currentTime()
    {
        return time();
    }
}

我想在此 class 中测试 public 函数 myFunction() 但我有点不知所措如何在不模拟 SUT 本身的情况下模拟 currentTime 方法(我的班级)

正确的做法是什么?我觉得使用单一方法 (getCurrentTime) 创建时间 class 并将其正确注入 MyClass 是多余的,因为我只检查代码中一个地方的时间。

这是最好的方法吗?

编辑: 我正在考虑制作一个 time 特性,因为它 looks like PhpUnit 可以模拟它。仍然不确定这对于在单一方法中使用是否过大..

我还有哪些其他选择?

我们可以在命名空间级别覆盖 time() 函数。检查 https://www.schmengler-se.de/en/2011/03/php-mocking-built-in-functions-like-time-in-unit-tests/

但缺点是每次我们调用 time() 它都会 return 模拟时间 :)

namespace MyClass;

function time()
{
    return MyClassTest::$mockTime ?: \time();
}

class MyClassTest extends TestCase{

    public static $mockTime;

    public function test_my_function(){   
        self::$mockTime = '08:10';
        $myClass = new MyClass();
        //$myClass->currentTime() //will return 08:10
        //do something with my function
    }

您可以创建已测试 class 的部分模拟对象,以便仅修改所选方法(currentTime 方法)的行为。为此,您可以使用 Mock Builder API:

setMethods

setMethods(array $methods) can be called on the Mock Builder object to specify the methods that are to be replaced with a configurable test double. The behavior of the other methods is not changed. If you call setMethods(NULL), then no methods will be replaced.

所以试试这个代码(假设 myFunction return isPending 方法的结果):

class MyClassTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @test
     */
    public function itShouldReturnTrueIfPendingState()
    {
        $currentTime = (new \DateTime('now -1 year'))->getTimestamp();

        /** @var MyClass|\PHPUnit_Framework_MockObject_MockObject $myClass */
        $myClass = $this->getMockBuilder(MyClass::class)
            ->disableOriginalConstructor()
            ->setMethods(['currentTime'])
            ->getMock();

        $myClass
            ->method('currentTime')
            ->willReturn($currentTime);

        $this->assertTrue($myClass->myFunction(time()));
    }

    /**
     * @test
     */
    public function itShouldReturnFalseIfNotState()
    {
        $currentTime = (new \DateTime('now +1 year'))->getTimestamp();


        /** @var MyClass|\PHPUnit_Framework_MockObject_MockObject $myClass */
        $myClass = $this->getMockBuilder(MyClass::class)
            ->disableOriginalConstructor()
            ->setMethods(['currentTime'])
            ->getMock();

        $myClass
            ->method('currentTime')
            ->willReturn($currentTime);

        $this->assertFalse($myClass->myFunction(time()));
    }

我知道我来晚了一点,但如果您能够在您的环境中安装 php 扩展,您可以使用 https://github.com/slope-it/clock-mock,它透明地工作,对您的修改为 0代码。那时,您可以删除 currentTime() 并只使用 time().