Проблема с тестированием с использованием withConsecutive()
У меня есть класс с методами
public function fillStatisticForAllTime(): void
{
$firstDate = \DateTime::createFromFormat('Y-m-d H:i:s', $this->userPaymentsRepository->getTheOldestDate());
$lastDate = (new \DateTime())->setTime(0, 0, 0);
while ($firstDate < $lastDate) {
$this->fillStatistic($firstDate);
$firstDate->add(new DateInterval('P1D'));
}
}
private function fillStatistic(DateTime $day): void
{
$dateFrom = $day->setTime(0, 0, 0);
$dateTo = clone $day;
$dateTo->setTime(23, 59, 59);
$statisticAll = $this->userPaymentsRepository
->getSummaryStatistic(['dateFrom' => $dateFrom, 'dateTo' => $dateTo, 'onlyPaid' => false]);
$this->userPaymentsStatisticRepository->insertDailyStatistic($statisticAll, $day, false);
$statisticOnlyPaid = $this->userPaymentsRepository
->getSummaryStatistic(['dateFrom' => $dateFrom, 'dateTo' => $dateTo, 'onlyPaid' => true]);
$this->userPaymentsStatisticRepository->insertDailyStatistic($statisticOnlyPaid, $day, true);
}
Я пытаюсь написать на него тесты:
public function testFillStatisticForAllTime(): void
{
$dateFromFirst = (new \DateTime())->sub(new DateInterval('P2D'))->setTime(0, 0, 0);
$dateToFirst = (new \DateTime())->sub(new DateInterval('P2D'))->setTime(23, 59, 59);
$dateFromSecond = (new \DateTime())->sub(new DateInterval('P1D'))->setTime(0, 0, 0);
$dateToSecond = (new \DateTime())->sub(new DateInterval('P1D'))->setTime(23, 59, 59);
$statistic = ['addPeopleFromPlugin' => 10];
$statisticOnlyPaid = ['addPeopleFromPlugin' => 7];
$this->userPaymentsRepositoryMock->expects($this->once())
->method('getTheOldestDate')
->willReturn($dateFromFirst->format('Y-m-d H:i:s'));
$this->userPaymentsRepositoryMock->expects($this->exactly(4))
->method('getSummaryStatistic')
->withConsecutive(
[['dateFrom' => $dateFromFirst, 'dateTo' => $dateToFirst, 'onlyPaid' => false]],
[['dateFrom' => $dateFromFirst, 'dateTo' => $dateToFirst, 'onlyPaid' => true]],
[['dateFrom' => $dateFromSecond, 'dateTo' => $dateToSecond, 'onlyPaid' => false]],
[['dateFrom' => $dateFromSecond, 'dateTo' => $dateToSecond, 'onlyPaid' => true]],
)
->willReturnOnConsecutiveCalls($statistic, $statisticOnlyPaid, $statistic, $statisticOnlyPaid);
$this->userPaymentsStatisticRepositoryMock->expects($this->exactly(4))
->method('insertDailyStatistic')
->withConsecutive(
[$statistic, $dateFromFirst, false],
[$statisticOnlyPaid, $dateFromFirst, true],
[$statistic, $dateFromSecond, false],
[$statisticOnlyPaid, $dateFromSecond, true],
);
$this->fillUserPaymentStatisticService->fillStatisticForAllTime();
}
Если я беру текущую дату и отнимаю от нее два дня то метод fillStatistic должен запустится 2 раза а методы репозиториев getSummaryStatistic и insertDailyStatistic 4 раза соответственно. Сегодня 2020-02-07, соответственно методы репозиториев первые два раза должны вызваться с датой 2020-02-05, и два раза с датой 2020-02-06, однако вместо этого я получаю фейл:
Parameter 0 for invocation #0 App\Repositories\Members\Billing\UserPaymentsRepository::getSummaryStatistic(Array (...)): array does not match expected value.
Expected:
Array (
'dateFrom' => 2020-02-05T00:00:00.000000+0000
'dateTo' => DateTime Object (...)
'onlyPaid' => false
)
Actual:
Array (
'dateFrom' => 2020-02-06T00:00:00.000000+0000
'dateTo' => DateTime Object (...)
'onlyPaid' => false
)
Пробовал ставить var_dump($firstDate) в начале цикла в методе fillStatisticForAllTime, дата та что ожидается, но почему в тесте фейлится? Видимо проблема как то связана с тем что в методе fillStatisticForAllTime я передаю один и тот же обьект даты, если же поменять на
while ($firstDate < $lastDate) {
$date = \DateTime::createFromFormat('U', $firstDate->format('U'));
$this->fillStatistic($date);
$firstDate->add(new \DateInterval('P1D'));
}
то все работает
Ответы (1 шт):
Вы создаёте $firstDate с датой и временем, затем передаёте этот объект в метод fillStatistic и в нём у того же самого объекта меняете время на 00:00:00. Затем вы сравниваете эту дату с нулевым временем с текущей датой, у которой время тоже сброшено на ноль, видимо, не учитывая что дату поменяли в другой функции.
$firstDate = \DateTime::createFromFormat('Y-m-d H:i:s', ...);
$this->fillStatistic($firstDate);
private function fillStatistic(DateTime $day): void
{
$dateFrom = $day->setTime(0, 0, 0);
// $dateFrom и $day здесь тот же самый объект
В такой ситуации не стоит ожидать такого поведения, какое вам нужно. Что вы и наблюдаете, когда заменяете объект даты на новый перед вызовом функции. Аналогично должно помочь отправлять копию объекта даты.
$this->fillStatistic(clone $firstDate);
Можно порекомендовать вам выделить логику работы с датами в отдельный класс или метод, который на входе будет получать дату начала и дату конца, выдавая последовательность дат типа DateTimeImmutable, для которых уже вы ведёте учёт. Такое выделение позволит существенно упростить тесты, устранив необходимость использовать в них моки: достаточно будет проверить только что для двух дат получается правильная последовательность.
Даже если не выделять эту логику в отдельную сущность, использование DateTimeImmutable и DateTimeImmutable::createFromMutable должно указать вам на все слабые места и устранить возможность появления таких ошибок в дальнейшем.