2017-01-25 11 views
0

Я использую Mockery в моем проекте на базе Laravel, чтобы помочь протестировать контроллер Laravel MVC. Ниже приведена соответствующая часть моего класса контроллера, который я пытаюсь проверить.Проблема с mockery при введении нескольких смешных объектов в контроллер

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. 

Но самое смешное и озадачивает дело в том, что если я комментарий и комбинация из 2 из 3 разделов издевательств, мой тестовый проход. Это означает, что мой код действительно работает правильно, но по какой-то причине в этом случае я не могу вставлять в проект один из нескольких моделей объектов модели и проверять их все сразу. I guess Я мог бы разделить это на три отдельных теста, которые гарантируют, что функция каждой модели add(...) вызывается, но я хочу сделать все это в одном случае, если это возможно. Я также знаю, что могу использовать шаблон репозитория, чтобы обернуть всю бизнес-логику в функции контроллера add(...) в один вызов, но затем я столкнулся бы с той же проблемой при тестировании класса репозитория.

+1

Попробуйте привязать mocks в setUp вместо тестовой функции. – nCrazed

+0

Я попробовал ваше предложение, я получаю то же самое поведение. Я не думаю, что мне хотелось бы привязать свои издевательства в 'setUp', так как мне не нужно делиться этими mocks для большинства моих тестовых случаев. – roundtheworld

+1

Как определяется маршрут? – nCrazed

ответ

1

Вы не издеваетесь над возвращаемыми значениями методов, чтобы эта строка пыталась получить доступ к атрибуту (id) на null.

$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.