使用 phpspec 测试命令处理程序
Testing command handler with phpspec
最近我尝试使用 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);
}
}
员工 class 我已经指定了。因此,也许在命令处理程序中,只需检查 Employee 的方法是否已被调用就足够了。你怎么看待这件事?我的方向好吗?
消息传递
确实,您不应该验证状态,而是期待对象之间的某些交互。毕竟,这就是 OOP 的意义所在 - 消息传递。
您在 PHPUnit 中完成的方式是状态验证。它会强制您公开状态,因为您需要提供 "getter",这并不总是需要的。您感兴趣的是员工的电子邮件已更新:
$employee->updateEmail(new Email('new@email.com'))->shouldBeCalled();
如果您愿意,间谍也可以实现同样的效果:
$employee->updateEmail(new Email('new@email.com'))->shouldHaveBeenCalled();
Command/Query分离
我们通常只需要声明我们对有副作用的方法(命令方法从Command/Query分离)的期望。我们嘲笑他们。
查询方法不需要模拟,而是存根。您真的不希望 EmployeeRepository::employeeWithUsername()
应该被调用。这样做我们正在对实现做出假设,这反过来会使重构变得更加困难。你所需要的只是存根,所以 if 调用它的方法 returns 结果:
$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);
}
}
最近我尝试使用 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);
}
}
员工 class 我已经指定了。因此,也许在命令处理程序中,只需检查 Employee 的方法是否已被调用就足够了。你怎么看待这件事?我的方向好吗?
消息传递
确实,您不应该验证状态,而是期待对象之间的某些交互。毕竟,这就是 OOP 的意义所在 - 消息传递。
您在 PHPUnit 中完成的方式是状态验证。它会强制您公开状态,因为您需要提供 "getter",这并不总是需要的。您感兴趣的是员工的电子邮件已更新:
$employee->updateEmail(new Email('new@email.com'))->shouldBeCalled();
如果您愿意,间谍也可以实现同样的效果:
$employee->updateEmail(new Email('new@email.com'))->shouldHaveBeenCalled();
Command/Query分离
我们通常只需要声明我们对有副作用的方法(命令方法从Command/Query分离)的期望。我们嘲笑他们。
查询方法不需要模拟,而是存根。您真的不希望 EmployeeRepository::employeeWithUsername()
应该被调用。这样做我们正在对实现做出假设,这反过来会使重构变得更加困难。你所需要的只是存根,所以 if 调用它的方法 returns 结果:
$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);
}
}