【问题标题】:How to mock date in php unit test?如何在 php 单元测试中模拟日期?
【发布时间】:2020-04-05 19:46:11
【问题描述】:

我是 php 单元测试的新手。如何在下面的函数中模拟日期。目前它正在获取当前日期。但我想将模拟中的日期更改为一个月的第一天。

function changeStartEndDate() {

    if (date('j', strtotime("now")) === '1') {

        $this->startDate = date("Y-n-j", strtotime("first day of previous month"));

        $this->endDate = date("Y-n-j", strtotime("last day of previous month")) . ')';
    } else {

        $this->startDate = date("Y-n-j", strtotime(date("Y-m-01")));
        $this->endDate = date("Y-n-j", strtotime("yesterday"));
    }
}

我试过这样做,但它不起作用。

public function testServicesChangeStartEndDate() {
    $mock = $this->getMockBuilder('CoreFunctions')
        ->setMethods(array('changeStartEndDate'))
        ->getMock();

    $mock->method('changeStartEndDate')
        ->with(date("Y-n-j", strtotime(date("Y-m-01"))));

    $this->assertSame(
        '1',
        $this->core->changeStartEndDate()
    );

}

【问题讨论】:

  • 您是在尝试测试 CoreFunction,还是将其用作不同测试用例的模拟?
  • 我正在尝试测试 CoreFunctions
  • 在这种情况下:您永远不需要模拟属于您的 CoreFunction 的任何内容。如果您为被测单元创建模拟,我认为这是一种代码气味。只有当您测试另一个使用您的核心功能的服务时,使用您的核心功能的模拟版本才有意义。我敢说对于这个测试用例,你不需要创建任何模拟。您正在尝试使用模拟来解决由于当前时间造成的副作用而遇到的问题。如果您将代码更改为没有副作用,则无需模拟任何内容。
  • 如果你在CoreFunctions 中遇到了你想模拟的东西,这强烈表明你的函数/类/方法做的太多了,你应该把它移到额外的服务中.进行单元测试时总是想知道:我要测试的实际单元是什么?它应该是明确定义的,充其量是一个纯函数。 CoreFunctions 这个名字暗示了一个 utils 库,它很可能已经在范围内被夸大了。

标签: php phpunit


【解决方案1】:

通过避免副作用,单元测试效果最好。 datestrtotime 都取决于您的主机系统上定义的外部状态,即当前时间。

解决这个问题的一种方法是使当前时间成为可注入属性,允许您“冻结”它或将其设置为特定值。

如果您查看strtotime 的定义,它允许设置当前时间:

strtotime ( string $time [, int $now = time() ] ) : int

date相同:

date ( string $format [, int $timestamp = time() ] ) : string

因此,请始终从函数中注入该值,以将代码结果与主机状态分离。

function changeStartEndDate($now) {

    if (date('j', strtotime("now", $now), $now) === '1') {
        ...
        $this->startDate = date("Y-n-j", strtotime(date("Y-m-01", $now), $now));
        $this->endDate = date("Y-n-j", strtotime("yesterday", $now), $now);
    }

你的函数是类的一部分吗?然后,我会将$now 作为构造函数的一部分,并将其默认为time()。在你的测试用例中,你总是可以注入一个固定的数字,它总是会返回相同的输出。

class MyClassDealingWithTime {
    private $now;

    public function __construct($now = time()) {
        $this->now = $now;
    }


    private customDate($format) {
        return date($format, $this->now);
    }

    private customStringToTime($timeSring) {
        return strtotime($timeStrimg, $this->now);
    }
}

然后在您的测试用例中将 $now 设置为您需要的值,例如通过

$firstDayOfAMonth = (new DateTime('2017-06-01'))->getTimestamp();
$testInstance = new MyClassDealingWithTime(firstDayOfAMonth);

$actual = $testInstance->publicMethodYouWantTotest();

... 

【讨论】:

  • 谢谢,我听从了你的建议,已经成功了。
  • @SimpleProgrammer 如果对您有帮助,您也可以接受答案:)
【解决方案2】:

免责声明:我编写了这个库

我正在添加一个答案以提供一种替代方法,该方法可以对您的代码进行零修改,并且无需注入当前时间。

如果你负担得起安装 php uopz 扩展,那么你可以使用https://github.com/slope-it/clock-mock

然后您可以在测试期间使用ClockMock::freezeClockMock::reset 将内部php 时钟“移动”到特定时间点。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-06-14
    • 2010-11-17
    • 1970-01-01
    • 1970-01-01
    • 2013-10-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多