模拟包装器时间函数


Mocking a wrapper time function

我已经在我的类中为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();
    }
}

我想在这个类中测试公共函数myFunction(),但我有点困惑如何在不模拟SUT本身(MyClass)的情况下模拟currentTime方法

正确的做法是什么?我觉得用单个方法(getCurrentTime)创建一个时间类并将其注入MyClass,虽然正确,但过度了,因为我只检查代码中一个地方的时间。

无论如何这是最好的方法吗?

编辑:我正在考虑使time特性,因为它看起来像PhpUnit可以模拟这一点。仍然不确定这对于在单个方法中使用是否过度…

我还有其他选择吗?

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

setMethods(array $methods)可以在Mock Builder对象上调用指定要用可配置测试替换的方法翻倍。其他方法的行为没有改变。如果你打电话setMethods(NULL),则不替换任何方法。

那么试试下面的代码(假设myFunction返回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()));
    }

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

,但缺点是,每次我们调用time(),它将返回模拟时间:)

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
    }

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