我在StackOverflow上看到的大多数答案都没有使用DateTime
对象,而是使用date()
函数。这使得它们成为非常脏的解决方案(覆盖date()
,模拟受测试对象的受保护功能等)。
有没有办法模仿DateTime
,有效地模拟当前的日期/时间?
例如,以下是我要测试的代码:
public function __construct(UserInterface $user, EntityManager $manager)
{
$this->user = $user;
$this->manager = $manager;
}
public function create(Tunnel $tunnel, $chain, $response)
{
$history = new CommandHistory();
$history->setTunnel($tunnel)
->setCommand($chain)
->setResponse($response)
->setUser($this->user)
;
$this->manager->persist($history);
$this->manager->flush();
}
以下是我在CommandHistory
课程中设置日期和时间的地方:
class CommandHistory
{
// Property definitions...
public function __construct()
{
$this->time = new \DateTime();
}
}
这是我的单元测试:
public function testCreate()
{
$user = new User();
$manager = $this->mockManagerWithUser($user);
$tunnel = $this->tunnel;
$chain = 'Commands`Chain';
$response = 'This is the response!';
$creator = new CommandHistoryCreator($user, $manager);
$creator->create($tunnel, $chain, $response);
}
protected function mockManagerWithUser(UserInterface $user)
{
$manager = \Mockery::mock('Doctrine\ORM\EntityManager');
$manager->shouldReceive('persist')->once()->with(\Mockery::on(function(CommandHistory $argument) use ($user) {
return
$argument->getCommand() === 'Commands`Chain'
&& $argument->getResponse() === 'This is the response!'
&& $argument->getTunnel() === $this->tunnel
&& $argument->getUser() === $user
;
}));
$manager->shouldReceive('flush')->once()->withNoArgs();
return $manager;
}
正如你所看到的,我创建了一个相当长篇大论的闭包只是为了排除包含当前时间的字段的比较,我觉得这会损害我测试的可读性。
另外,为了保持使用此课程的人的易用性,我不想让他们在当前时间内通过create()
功能。我认为在我的课程中添加奇怪的行为只是为了让它们可以测试,这意味着我做错了。
答案 0 :(得分:11)
因此,解决此问题的标准方法依赖于接受在当前实现中对对象提供静态,隐式,未声明的依赖关系,该对象提供当前时间(包含在DateTime对象的新实例中)。如果您使用自己的代码(而不是框架/语言中的类)执行此操作,则无法轻松测试。
解决方案是停止使用隐式未声明的依赖关系并明确声明隐式依赖关系。我会通过创建DateTimeProvider
(或DateTimeFactory
)接口来实现此目的,该接口具有方法GetCurrentDateTime
。将其传递到CommandHistoryCreator
的构造函数中,并将其传递给CommandHistory
构造函数。 CommandHistory
然后会要求提供者获取当前日期时间对象,而不是自己创建一个新对象,并且可以继续保持原样。
这样您就可以在测试中提供模拟DateTime
,并检查CommandHistory
是否保留了正确的DateTime