模拟包装时间函数

时间:2016-10-28 10:16:26

标签: php unit-testing testing phpunit

我为我的班级

中的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特征因为looks like PhpUnit可以嘲笑这个。仍然不确定这在单一方法中是否过度使用..

我还有其他选择吗?

3 个答案:

答案 0 :(得分:1)

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

  

可以在Mock Builder对象上调用setMethods(array $ methods)   指定要用可配置测试替换的方法   双。其他方法的行为不会改变。如果你打电话   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()));
    }

答案 1 :(得分:0)

我们可以在命名空间级别覆盖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
    }

答案 2 :(得分:0)

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