将多个模拟对象注入控制器时,Mockery出现问题

时间:2017-01-25 23:42:41

标签: php laravel phpunit mockery

我在基于Laravel的PHP项目中使用Mockery来帮助测试Laravel MVC控制器。下面是我试图测试的控制器类的相关部分。

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(...)函数中的所有业务逻辑包装到一个调用中,但是在测试存储库类时我会遇到同样的问题。

1 个答案:

答案 0 :(得分:1)

您没有模拟方法的返回值,因此此行尝试访问null上的属性(id)。

   $newDeviceId = $this->deviceModel->add($name, $description, $type)->id;

您可以通过向Device模型模拟添加返回值来解决此问题:

$mockDeviceModel = Mockery::mock(Device::class);
$device = new Device;
$mockDeviceModel->shouldReceive('add')->withAnyArgs()->once()->andReturn($device);

为了使这些问题在将来更容易调试,请更改错误处理程序,以便在测试环境中重新抛出异常,而不是呈现有效的HTML响应。