I have a class FillUserPaymentStatisticService with methods:
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); } }
And I try write test:
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(); }
And i have a fail:
Parameter 0 for invocation #0 AppRepositoriesMembersBillingUserPaymentsRepository::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 )
If today 2020-02-07 then first date must be 2020-02-06 and second 2020-02-05, why in test the date is 2020-02-06?
When I change method fillStatisticForAllTime like:
while ($firstDate < $lastDate) { $date = DateTime::createFromFormat('U', $firstDate->format('U')); $this->fillStatistic($date); $firstDate->add(new DateInterval('P1D')); }
its become work correct
Advertisement
Answer
The main issue here is that your variables are mutable objects (passed by a reference).
Also, mock assertions are being checked after a test and some of your variables have changed.
FIX: Clone any date (“detach” from the original variable) before using it.
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(clone $firstDate); // <--------------- clone date $firstDate->add(new DateInterval('P1D')); } } private function fillStatistic(DateTime $day): void { $dateFrom = clone $day; // <----------------------------------- clone date $dateFrom->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); }
Btw, your 2nd approach works because you create a new object (“detact” from the original variable)
To avoid similar bugs in future you may want to use immutable date objects.