将多个模拟对象注入控制器时出现 Mockery 问题
Issue with Mockery when injecting multiple mocked objects into controller
我在基于 Laravel 的 PHP 项目中使用 Mockery 来帮助测试 Laravel MVC 控制器。下面是我的控制器的相关部分 class 我正在尝试测试。
class DevicesController extends Controller
{
private $deviceModel;
private $rfDeviceModel;
private $userModel;
private $userDeviceModel;
public function __construct(Device $deviceModel, RFDevice $rfDeviceModel, User $userModel, UserDevice $userDeviceModel)
{
$this->middleware('guest');
$this->deviceModel = $deviceModel;
$this->rfDeviceModel = $rfDeviceModel;
$this->userModel = $userModel;
$this->userDeviceModel = $userDeviceModel;
}
...
public function add(Request $request)
{
$name = $request->input('name');
$description = $request->input('description');
$onCode = $request->input('onCode');
$offCode = $request->input('offCode');
$pulseLength = $request->input('pulseLength');
$type = 1;
$currentUserId = $this->currentUser()->id;
$newDeviceId = $this->deviceModel->add($name, $description, $type)->id;
$this->rfDeviceModel->add($onCode, $offCode, $pulseLength, $newDeviceId);
$this->userDeviceModel->add($currentUserId, $newDeviceId);
return redirect()->route('devices');
}
}
特别是,我正在围绕控制器的 add(Request $request)
函数编写多个单元测试,以确保三个模型 add(...)
函数中的每一个都被调用。我处理此问题的测试用例如下所示:
public function testAdd_CallsAddForModels()
{
$mockDeviceModel = Mockery::mock(Device::class);
$mockDeviceModel->shouldReceive('add')->withAnyArgs()->once();
$this->app->instance(Device::class, $mockDeviceModel);
$mockRFDeviceModel = Mockery::mock(RFDevice::class);
$mockRFDeviceModel->shouldReceive('add')->withAnyArgs()->once();
$this->app->instance(RFDevice::class, $mockRFDeviceModel);
$mockUserDeviceModel = Mockery::mock(UserDevice::class);
$mockUserDeviceModel->shouldReceive('add')->withAnyArgs()->once();
$this->app->instance(UserDevice::class, $mockUserDeviceModel);
$user = $this->givenSingleUserExists();
$this->addDeviceForUser($user->user_id);
}
private function givenSingleUserExists()
{
$user = new User;
$name = self::$faker->name();
$email = self::$faker->email();
$userId = self::$faker->uuid();
$user = $user->add($name, $email, $userId);
return $user;
}
private function addDeviceForUser($userId)
{
$this->withSession([env('SESSION_USER_ID') => $userId])
->call('POST', '/devices/add', [
'name' => 'Taylor',
'description' => 'abcd',
'onCode' => 1,
'offCode' => 2,
'pulseLength' => 3
]);
}
当我运行这个测试时,我在控制台中得到以下输出:
There was 1 error:
1) Tests\Unit\Controller\DeviceControllerTest::testAdd_CallsAddForModels
Mockery\Exception\InvalidCountException: Method add() from Mockery_1_App_RFDevice should be called
exactly 1 times but called 0 times.
但有趣且令人费解的是,如果我注释掉 3 个嘲弄部分中的 2 个并组合,我的测试就会通过。这意味着,我的代码实际上工作正常,但在这种情况下出于某种原因,我无法将多个模拟模型对象注入我的控制器并一次测试它们。我 猜测 我可以将其分成三个单独的测试,以确保调用每个模型的 add(...)
函数,但如果可能的话,我想在一个测试用例中完成所有测试。我也知道我可以使用存储库模式将控制器的 add(...)
函数中的所有业务逻辑包装到一个调用中,但是我会 运行 在测试存储库时遇到同样的问题 class.
您没有模拟方法的 return 值,因此此行试图访问 null
.
上的属性 (id)
$newDeviceId = $this->deviceModel->add($name, $description, $type)->id;
您可以通过向 Device
模型模拟添加 return 值来解决此问题,如下所示:
$mockDeviceModel = Mockery::mock(Device::class);
$device = new Device;
$mockDeviceModel->shouldReceive('add')->withAnyArgs()->once()->andReturn($device);
为了使此类问题在将来更容易调试,请将错误处理程序更改为 re-throw 测试环境中的异常,而不是呈现有效的 HTML 响应。
我在基于 Laravel 的 PHP 项目中使用 Mockery 来帮助测试 Laravel MVC 控制器。下面是我的控制器的相关部分 class 我正在尝试测试。
class DevicesController extends Controller
{
private $deviceModel;
private $rfDeviceModel;
private $userModel;
private $userDeviceModel;
public function __construct(Device $deviceModel, RFDevice $rfDeviceModel, User $userModel, UserDevice $userDeviceModel)
{
$this->middleware('guest');
$this->deviceModel = $deviceModel;
$this->rfDeviceModel = $rfDeviceModel;
$this->userModel = $userModel;
$this->userDeviceModel = $userDeviceModel;
}
...
public function add(Request $request)
{
$name = $request->input('name');
$description = $request->input('description');
$onCode = $request->input('onCode');
$offCode = $request->input('offCode');
$pulseLength = $request->input('pulseLength');
$type = 1;
$currentUserId = $this->currentUser()->id;
$newDeviceId = $this->deviceModel->add($name, $description, $type)->id;
$this->rfDeviceModel->add($onCode, $offCode, $pulseLength, $newDeviceId);
$this->userDeviceModel->add($currentUserId, $newDeviceId);
return redirect()->route('devices');
}
}
特别是,我正在围绕控制器的 add(Request $request)
函数编写多个单元测试,以确保三个模型 add(...)
函数中的每一个都被调用。我处理此问题的测试用例如下所示:
public function testAdd_CallsAddForModels()
{
$mockDeviceModel = Mockery::mock(Device::class);
$mockDeviceModel->shouldReceive('add')->withAnyArgs()->once();
$this->app->instance(Device::class, $mockDeviceModel);
$mockRFDeviceModel = Mockery::mock(RFDevice::class);
$mockRFDeviceModel->shouldReceive('add')->withAnyArgs()->once();
$this->app->instance(RFDevice::class, $mockRFDeviceModel);
$mockUserDeviceModel = Mockery::mock(UserDevice::class);
$mockUserDeviceModel->shouldReceive('add')->withAnyArgs()->once();
$this->app->instance(UserDevice::class, $mockUserDeviceModel);
$user = $this->givenSingleUserExists();
$this->addDeviceForUser($user->user_id);
}
private function givenSingleUserExists()
{
$user = new User;
$name = self::$faker->name();
$email = self::$faker->email();
$userId = self::$faker->uuid();
$user = $user->add($name, $email, $userId);
return $user;
}
private function addDeviceForUser($userId)
{
$this->withSession([env('SESSION_USER_ID') => $userId])
->call('POST', '/devices/add', [
'name' => 'Taylor',
'description' => 'abcd',
'onCode' => 1,
'offCode' => 2,
'pulseLength' => 3
]);
}
当我运行这个测试时,我在控制台中得到以下输出:
There was 1 error:
1) Tests\Unit\Controller\DeviceControllerTest::testAdd_CallsAddForModels
Mockery\Exception\InvalidCountException: Method add() from Mockery_1_App_RFDevice should be called
exactly 1 times but called 0 times.
但有趣且令人费解的是,如果我注释掉 3 个嘲弄部分中的 2 个并组合,我的测试就会通过。这意味着,我的代码实际上工作正常,但在这种情况下出于某种原因,我无法将多个模拟模型对象注入我的控制器并一次测试它们。我 猜测 我可以将其分成三个单独的测试,以确保调用每个模型的 add(...)
函数,但如果可能的话,我想在一个测试用例中完成所有测试。我也知道我可以使用存储库模式将控制器的 add(...)
函数中的所有业务逻辑包装到一个调用中,但是我会 运行 在测试存储库时遇到同样的问题 class.
您没有模拟方法的 return 值,因此此行试图访问 null
.
$newDeviceId = $this->deviceModel->add($name, $description, $type)->id;
您可以通过向 Device
模型模拟添加 return 值来解决此问题,如下所示:
$mockDeviceModel = Mockery::mock(Device::class);
$device = new Device;
$mockDeviceModel->shouldReceive('add')->withAnyArgs()->once()->andReturn($device);
为了使此类问题在将来更容易调试,请将错误处理程序更改为 re-throw 测试环境中的异常,而不是呈现有效的 HTML 响应。