如何测试创建模型服务?
How to test create model service?
我决定创建 CreateClassroomService 以在我的控制器方法中分离逻辑。
class CreateClassroomService extends Service
{
public function create(string $name, User $user): ?Classroom
{
$this->checkName($name);
$classroom = new Classroom();
$created = $classroom->setName($name)
->associateUser($user)
->save();
return $created ? $classroom : null;
}
private function checkName(string $name): void
{
if (empty($name)) {
throw new InvalidArgumentException();
}
}
}
我正在尝试将此服务作为学习单元测试的一部分进行测试,但我不知道如何进行。我不知道如何模拟教室对象来控制方法应该 return。这是否意味着创建此服务不是一个好主意,因为我无法对其进行测试?我应该以不同的方式构建此服务吗?除非服务可以测试,但我不知道如何...我应该在断言中检查什么?
这是我的测试,但效果不佳,因为我无法强制执行 returned 的内容。
public function testGivenCreateCorrectDataClassroomWillBeCreated(): void
{
$name = 'Test classroom';
$user = Mockery::mock(User::class);
$result = $this->service->create($name, $user);
$this->assertTrue($result);
}
不确定模拟在 Mockery 中是如何工作的,但让我们回顾一下您在做什么:您尝试测试的创建函数创建了一个 Classroom
的新实例,并且由于它实例化了内联对象无法控制它。
根据您编写的代码,我假设您构建了一个 classroom 对象和 return,不再使用相同的创建者实例,因此您可以做的是添加一个构造函数到 CreateClassroomService
,您可以通过它 注入 一个 Classroom
对象。如果您使用相同的创建服务一次创建多个 classroom 对象,您还需要确保在创建之间以某种方式将 classroom 实例重置为其默认状态,或者您在再次调用 create
之前注入一个全新的 classroom 对象。不过,这完全取决于 class 房间的功能。
然后可以通过单元测试模拟 classroom 对象——注入模拟实例就可以了。您实际上已经在注入 User
对象,所以您已经在正确的路线上了!
class CreateClassroomService extends Service {
private ?Classroom $classroom;
public function __construct(Classroom $classroom) {
$this->classroom = $classroom;
}
...
}
现在您只需要在测试中模拟您的 classroom 对象,在实例化对象时将模拟注入您的服务,然后就可以开始了。 :)
顺便说一句,您可能想考虑以接口的形式对您的服务进行更多抽象,或者查看您的父服务 class 以使其总体上更易于单元测试。一般来说,要进行有效的单元测试,您需要避免使用静态方法和 new
关键字;在你绝对无法避免的地方,一种方法可能是将那一行代码包装在一个封装的方法中,这样你就可以模拟该方法而不是 return 你模拟数据/模拟实例(但通常如果你必须这样做,这应该是一个明显的标志,提醒您拥有低于标准的架构。
对于这样的事情,您可以简单地断言 ClassRoom 实际上已经创建。文档
根据 docs,更新您的测试 class 以便它使用 RefreshDatabase
特征,例如:
use Illuminate\Foundation\Testing\RefreshDatabase;
class ExampleTest extends TestCase
{
use RefreshDatabase;
Laravel 有 Model Factories 可以非常快速和轻松地使用虚拟数据创建模型。应该已经为您创建了一个 UserFactory
(如果您更新了默认用户迁移,您可能需要更新它)。
public function testGivenCreateCorrectDataClassroomWillBeCreated(): void
{
$name = 'Test classroom';
$user = User::factory()->create();
$result = $this->service->create($name, $user);
$this->assertInstanceOf(ClassRoom::class, $result);
$this->assertDatabaseHas('class_rooms', ['name' => $name]);
}
不要忘记将 User
和 ClassRoom
模型导入到您的测试中 class。
我决定创建 CreateClassroomService 以在我的控制器方法中分离逻辑。
class CreateClassroomService extends Service
{
public function create(string $name, User $user): ?Classroom
{
$this->checkName($name);
$classroom = new Classroom();
$created = $classroom->setName($name)
->associateUser($user)
->save();
return $created ? $classroom : null;
}
private function checkName(string $name): void
{
if (empty($name)) {
throw new InvalidArgumentException();
}
}
}
我正在尝试将此服务作为学习单元测试的一部分进行测试,但我不知道如何进行。我不知道如何模拟教室对象来控制方法应该 return。这是否意味着创建此服务不是一个好主意,因为我无法对其进行测试?我应该以不同的方式构建此服务吗?除非服务可以测试,但我不知道如何...我应该在断言中检查什么?
这是我的测试,但效果不佳,因为我无法强制执行 returned 的内容。
public function testGivenCreateCorrectDataClassroomWillBeCreated(): void
{
$name = 'Test classroom';
$user = Mockery::mock(User::class);
$result = $this->service->create($name, $user);
$this->assertTrue($result);
}
不确定模拟在 Mockery 中是如何工作的,但让我们回顾一下您在做什么:您尝试测试的创建函数创建了一个 Classroom
的新实例,并且由于它实例化了内联对象无法控制它。
根据您编写的代码,我假设您构建了一个 classroom 对象和 return,不再使用相同的创建者实例,因此您可以做的是添加一个构造函数到 CreateClassroomService
,您可以通过它 注入 一个 Classroom
对象。如果您使用相同的创建服务一次创建多个 classroom 对象,您还需要确保在创建之间以某种方式将 classroom 实例重置为其默认状态,或者您在再次调用 create
之前注入一个全新的 classroom 对象。不过,这完全取决于 class 房间的功能。
然后可以通过单元测试模拟 classroom 对象——注入模拟实例就可以了。您实际上已经在注入 User
对象,所以您已经在正确的路线上了!
class CreateClassroomService extends Service {
private ?Classroom $classroom;
public function __construct(Classroom $classroom) {
$this->classroom = $classroom;
}
...
}
现在您只需要在测试中模拟您的 classroom 对象,在实例化对象时将模拟注入您的服务,然后就可以开始了。 :)
顺便说一句,您可能想考虑以接口的形式对您的服务进行更多抽象,或者查看您的父服务 class 以使其总体上更易于单元测试。一般来说,要进行有效的单元测试,您需要避免使用静态方法和 new
关键字;在你绝对无法避免的地方,一种方法可能是将那一行代码包装在一个封装的方法中,这样你就可以模拟该方法而不是 return 你模拟数据/模拟实例(但通常如果你必须这样做,这应该是一个明显的标志,提醒您拥有低于标准的架构。
对于这样的事情,您可以简单地断言 ClassRoom 实际上已经创建。文档
根据 docs,更新您的测试 class 以便它使用 RefreshDatabase
特征,例如:
use Illuminate\Foundation\Testing\RefreshDatabase;
class ExampleTest extends TestCase
{
use RefreshDatabase;
Laravel 有 Model Factories 可以非常快速和轻松地使用虚拟数据创建模型。应该已经为您创建了一个 UserFactory
(如果您更新了默认用户迁移,您可能需要更新它)。
public function testGivenCreateCorrectDataClassroomWillBeCreated(): void
{
$name = 'Test classroom';
$user = User::factory()->create();
$result = $this->service->create($name, $user);
$this->assertInstanceOf(ClassRoom::class, $result);
$this->assertDatabaseHas('class_rooms', ['name' => $name]);
}
不要忘记将 User
和 ClassRoom
模型导入到您的测试中 class。