最近我尝试了phpspec。它运行良好,但我遇到了测试命令处理程序的问题。例如,在PHPUnit中,我就这样测试:
/**
* @test
*/
public function it_should_change_an_email()
{
$this->repository->add($this->employee);
$this->handler->changeEmail(
new ChangeEmailCommand(
$this->employee->username()->username(),
'new@email.com'
)
);
Asserts::assertEquals(new Email('new@email.com'), $this->employee->email());
}
并设置:
protected function setUp()
{
$this->repository = new InMemoryEmployeeRepository();
$this->createEmployee();
$this->handler = new EmployeeCommandHandler($this->repository);
}
重点是此测试在Employee对象上进行断言以检查CommandHandler是否正常工作。但是在phpspec中,我不能在指定的对象上进行断言,在这种情况下,我只能在我的CommandHandler上进行断言。那么如何在phpspec中测试命令处理程序呢?
修改
也许间谍是要走的路:
class EmployeeCommandHandlerSpec extends ObjectBehavior
{
const USERNAME = 'johnny';
/** @var EmployeeRepository */
private $employeeRepository;
public function let(EmployeeRepository $employeeRepository)
{
$this->employeeRepository = $employeeRepository;
$this->beConstructedWith($employeeRepository);
}
public function it_changes_the_employee_email(Employee $employee)
{
$this->givenEmployeeExists($employee);
$this->changeEmail(
new ChangeEmailCommand(self::USERNAME, 'new@email.com')
);
$employee->changeEmail(new Email('new@email.com'))->shouldHaveBeenCalled();
}
private function givenEmployeeExists(Employee $employee)
{
$this->employeeRepository->employeeWithUsername(new EmployeeUsername(self::USERNAME))
->shouldBeCalled()
->willReturn($employee);
}
}
员工班级I已经入职。所以,也许,在命令处理程序中,只需检查是否已调用Employee的方法即可。你怎么看待这件事?我是朝着好的方向前进的吗?
答案 0 :(得分:3)
<强>信息强>
实际上,您不应该验证状态,而是期望对象之间的某些交互。这就是OOP关于事后的消息。
您在PHPUnit中完成它的方式是状态验证。它会强制你暴露状态,因为你需要提供一个&#34; getter&#34;,这并不总是需要的。您感兴趣的是员工的电子邮件已更新:
$employee->updateEmail(new Email('new@email.com'))->shouldBeCalled();
如果您愿意,可以通过间谍实现同样的目标:
$employee->updateEmail(new Email('new@email.com'))->shouldHaveBeenCalled();
命令/查询分离
我们通常只需要对具有副作用的方法(命令/查询分离的命令方法)说明我们的期望。我们嘲笑他们。
查询方法不需要模拟,而是存根。你真的不希望调用EmployeeRepository::employeeWithUsername()
。这样做我们就实施做出了假设,这反过来会使重构变得更难。所有你需要的是存根,所以如果一个方法被调用它会返回一个结果:
$employeeRepository->employeeWithUsername(new EmployeeUsername(self::USERNAME))
->willReturn($employee);
完整示例
class EmployeeCommandHandlerSpec extends ObjectBehavior
{
const USERNAME = 'johnny';
public function let(EmployeeRepository $employeeRepository)
{
$this->beConstructedWith($employeeRepository);
}
public function it_changes_the_employee_email(
EmployeeRepository $employees, Employee $employee
) {
$this->givenEmployeeExists($employees, $employee);
$this->changeEmail(
new ChangeEmailCommand(self::USERNAME, 'new@email.com')
);
$employee->changeEmail(new Email('new@email.com'))->shouldHaveBeenCalled();
}
private function givenEmployeeExists(
EmployeeRepository $employees, Employee $employee
) {
$employees->employeeWithUsername(new EmployeeUsername(self::USERNAME))
->willReturn($employee);
}
}