【问题标题】:Mocking a wrapper time function模拟包装器时间函数
【发布时间】:2016-10-28 10:16:26
【问题描述】:

我在我的类中为 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 本身的情况下模拟 currentTime 方法(MyClass)

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

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

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

我还有什么其他选择?

【问题讨论】:

    标签: php unit-testing testing phpunit


    【解决方案1】:

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

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

    【讨论】:

    • 我决定创建一个类并将其注入部分模拟 SUT,尽管您的建议有助于解决不同的问题,所以谢谢
    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2015-03-07
        • 1970-01-01
        • 2020-05-02
        • 1970-01-01
        • 2010-11-13
        • 1970-01-01
        • 2019-04-09
        相关资源
        最近更新 更多