dbudwin/RoboHome-Web

View on GitHub
tests/Unit/Controller/Web/DevicesControllerTest.php

Summary

Maintainability
C
1 day
Test Coverage
File `DevicesControllerTest.php` has 291 lines of code (exceeds 250 allowed). Consider refactoring.
<?php
 
namespace Tests\Unit\Controller\Web;
 
use App\Device;
use App\Repositories\IDeviceRepository;
use App\User;
use Illuminate\Foundation\Testing\TestResponse;
use Mockery;
use Tests\Unit\Controller\Common\DevicesControllerTestCase;
use Webpatser\Uuid\Uuid;
 
`DevicesControllerTest` has 32 functions (exceeds 20 allowed). Consider refactoring.
The class DevicesControllerTest has 20 public methods. Consider refactoring DevicesControllerTest to keep number of public methods under 10.
The class DevicesControllerTest has 32 non-getter- and setter-methods. Consider refactoring DevicesControllerTest to keep number of methods under 25.
class DevicesControllerTest extends DevicesControllerTestCase
{
public function testDevices_GivenUserNotLoggedIn_RedirectToLogin(): void
{
$response = $this->get('/devices');
 
$this->assertRedirectedToRouteWith302($response, '/login');
}
 
public function testDevices_GivenUserLoggedIn_ViewContainsUsersName(): void
{
$user = $this->makeUser();
 
$response = $this->actingAs($user)->get('/devices');
 
$response->assertSee($user->name . '\'s Controllable Devices');
$response->assertSuccessful();
}
 
The method testDevices_GivenUserLoggedIn_ViewContainsUsersDevices() has 30 lines of code. Current threshold is set to 25. Avoid really long methods.
public function testDevices_GivenUserLoggedIn_ViewContainsUsersDevices(): void
{
$user = $this->makeUser();
$deviceName = self::$faker->word();
$deviceDescription = self::$faker->sentence();
$htmlAttributeName = self::$faker->word();
$htmlAttributeValue = self::$faker->randomDigit();
$htmlAttribute = 'data-device-' . $htmlAttributeName . '=' . $htmlAttributeValue;
 
$mockDevice = Mockery::mock(Device::class);
$mockDevice
Line exceeds 120 characters; contains 121 characters
->shouldReceive('getAttribute')->with('public_id')->atLeast()->once()->andReturn(self::$faker->randomDigit())
->shouldReceive('getAttribute')->with('name')->atLeast()->once()->andReturn($deviceName)
->shouldReceive('getAttribute')->with('description')->atLeast()->once()->andReturn($deviceDescription)
->shouldReceive('htmlDataAttributesForSpecificDevice')->once()->andReturn([ $htmlAttribute ]);
 
$this->app->instance(Device::class, $mockDevice);
 
$mockUser = Mockery::mock(User::class);
$mockUser
->shouldReceive('getAttribute')->with('name')->once()->andReturn($user->name)
->shouldReceive('getAttribute')->with('devices')->once()->andReturn([$mockDevice]);
 
$response = $this->actingAs($mockUser)->get('/devices');
 
$response->assertSee($htmlAttribute);
$response->assertSee($deviceName);
$response->assertSee($deviceDescription);
$response->assertSuccessful();
}
 
public function testAdd_GivenPostedData_CallsAddOnModelsThenRedirectsToDevices(): void
{
$user = $this->makeUser();
$device = $this->makeDevice();
 
$response = $this->callAdd($device, $user);
 
$this->assertRedirectedToRouteWith302($response, '/devices');
}
 
public function testAdd_GivenSingleUserExists_SessionContainsSuccessMessage(): void
{
$user = $this->makeUser();
$device = $this->makeDevice();
 
$response = $this->callAdd($device, $user);
 
$response->assertSessionHas('alert-success');
}
 
public function testDelete_GivenUserDoesNotOwnDevice_RedirectToDevices(): void
{
$response = $this->callDeleteOnDeviceUserDoesNotOwn();
 
$this->assertRedirectedToRouteWith302($response, '/devices');
}
 
public function testDelete_GivenUserDoesNotOwnDevice_SessionContainsErrorMessage(): void
{
$response = $this->callDeleteOnDeviceUserDoesNotOwn();
 
$response->assertSessionHas('alert-danger');
}
 
public function testDelete_GivenUserOwnsDevice_RedirectToDevices(): void
{
$response = $this->callDeleteOnDeviceUserOwns();
 
$this->assertRedirectedToRouteWith302($response, '/devices');
}
 
public function testDelete_GivenUserOwnsDevice_SessionContainsSuccessMessage(): void
{
$response = $this->callDeleteOnDeviceUserOwns();
 
$response->assertSessionHas('alert-success');
}
 
public function testDelete_GivenUserOwnsDeviceThatHasAlreadyBeenDeleted_SessionContainsErrorMessage(): void
{
$response = $this->callDeleteOnDeviceUserOwns(false);
 
$response->assertSessionHas('alert-danger');
}
 
public function testDelete_GivenInvalidUrlParameter_Returns404(): void
{
$user = $this->makeUser();
$invalidUrlParameter = (string)self::$faker->randomNumber();
 
$response = $this->actingAs($user)->get("/devices/delete/$invalidUrlParameter");
 
$response->assertNotFound();
}
 
public function testUpdate_GivenUserDoesNotOwnDevice_RedirectToDevices(): void
{
$device = $this->makeDevice();
 
$mockUser = $this->mockUserOwnsDevice($device->id, false);
 
$response = $this->callUpdateOnDeviceUserDoesNotOwn($mockUser, $device);
 
$this->assertRedirectedToRouteWith302($response, '/devices');
}
 
public function testUpdate_GivenUserDoesNotOwnDevice_SessionContainsErrorMessage(): void
{
$device = $this->makeDevice();
 
$mockUser = $this->mockUserOwnsDevice($device->id, false);
 
$response = $this->callUpdateOnDeviceUserDoesNotOwn($mockUser, $device);
 
$response->assertSessionHas('alert-danger');
}
 
public function testUpdate_GivenUserOwnsDevice_RedirectToDevices(): void
{
$device = $this->makeDevice();
 
$mockUser = $this->mockUserOwnsDevice($device->id, true);
 
$response = $this->callUpdateOnDeviceUserOwns($mockUser, $device);
 
$this->assertRedirectedToRouteWith302($response, '/devices');
}
 
public function testUpdate_GivenUserOwnsDevice_SessionContainsSuccessMessage(): void
{
$device = $this->makeDevice();
 
$mockUser = $this->mockUserOwnsDevice($device->id, true);
 
$response = $this->callUpdateOnDeviceUserOwns($mockUser, $device);
 
$response->assertSessionHas('alert-success');
}
 
public function testUpdate_GivenInvalidUrlParameter_Returns404(): void
{
$user = $this->makeUser();
$invalidUrlParameter = (string)self::$faker->randomNumber();
 
$response = $this->putToUpdateWithUserAndDeviceId($user, $invalidUrlParameter);
 
$response->assertNotFound();
}
 
public function testHandleControlRequest_GivenUserExistsWithNoDevices_PublishIsNotCalled(): void
{
$device = $this->makeDevice();
$publicDeviceId = $device->public_id;
$action = self::$faker->word();
 
$mockUser = $this->mockUserOwnsDevice($device->id, false);
$mockUser->shouldReceive('getAttribute')->with('id')->never();
 
$this->mockMessagePublisher(0);
$this->givenGetForPublicIdCalledForDevice($device);
 
$response = $this->callControl($mockUser, $action, $publicDeviceId);
 
$this->assertRedirectedToRouteWith302($response, '/devices');
}
 
public function testHandleControlRequest_GivenUserExistsWithNoDevices_SessionContainsErrorMessage(): void
{
$device = $this->makeDevice();
$publicDeviceId = $device->public_id;
$action = self::$faker->word();
 
$mockUser = $this->mockUserOwnsDevice($device->id, false);
 
$this->givenGetForPublicIdCalledForDevice($device);
 
$response = $this->callControl($mockUser, $action, $publicDeviceId);
 
$response->assertSessionHas('alert-danger');
}
 
public function testHandleControlRequest_GivenUserExistsWithDevice_CallsPublish(): void
{
$device = $this->makeDevice();
$publicUserId = $device->public_id;
$action = self::$faker->word();
 
$mockUser = $this->mockUserOwnsDevice($device->id, true);
$mockUser->shouldReceive('getAttribute')->with('public_id')->once()->andReturn($publicUserId);
 
$this->givenGetForPublicIdCalledForDevice($device);
 
$this->mockMessagePublisher(1);
 
$response = $this->callControl($mockUser, $action, $publicUserId);
 
$this->assertRedirectedToRouteWith302($response, '/devices');
}
 
public function testHandleControlRequest_GivenInvalidUrlParameter_Returns404(): void
{
$user = $this->makeUser();
$invalidUrlParameter = (string)self::$faker->randomNumber();
$action = self::$faker->word();
 
$response = $this->callControl($user, $action, $invalidUrlParameter);
 
$response->assertNotFound();
}
 
private function callAdd(Device $device, User $user): TestResponse
{
$this->givenActionCalledOnDeviceRepository($device, 'create');
 
$csrfToken = self::$faker->uuid();
 
return $this->actingAs($user)->withSession(['_token' => $csrfToken])
->post('/devices/add', [
'name' => $device->name,
'description' => self::$faker->sentence(),
'on_code' => self::$faker->randomDigit(),
'off_code' => self::$faker->randomDigit(),
'pulse_length' => self::$faker->randomDigit(),
'_token' => $csrfToken
]);
}
 
private function callDeleteOnDeviceUserDoesNotOwn(): TestResponse
{
$device = $this->makeDevice();
$deviceId = $device->id;
$publicDeviceId = $device->public_id;
 
$mockUser = $this->mockUserOwnsDevice($deviceId, false);
 
$mockDeviceRepository = Mockery::mock(IDeviceRepository::class);
Similar blocks of code found in 2 locations. Consider refactoring.
$mockDeviceRepository
->shouldReceive('name')->never()->with($deviceId)
->shouldReceive('getForPublicId')->with(Mockery::on(function (Uuid $argument) use ($publicDeviceId) {
return $argument instanceof Uuid && $argument == Uuid::import($publicDeviceId);
}))->once()->andReturn($device)
->shouldReceive('delete')->never()->with($deviceId);
 
$this->app->instance(IDeviceRepository::class, $mockDeviceRepository);
 
return $this->actingAs($mockUser)->get("/devices/delete/$publicDeviceId");
}
 
The method callDeleteOnDeviceUserOwns has a boolean flag argument $wasDeleteSuccessful, which is a certain sign of a Single Responsibility Principle violation.
private function callDeleteOnDeviceUserOwns(bool $wasDeleteSuccessful = true): TestResponse
{
$device = $this->makeDevice();
$deviceId = $device->id;
$publicDeviceId = $device->public_id;
 
$mockUser = $this->mockUserOwnsDevice($deviceId, true);
 
$mockDeviceRepository = Mockery::mock(IDeviceRepository::class);
Similar blocks of code found in 2 locations. Consider refactoring.
$mockDeviceRepository
->shouldReceive('name')->once()->with($deviceId)
->shouldReceive('getForPublicId')->with(Mockery::on(function (Uuid $argument) use ($publicDeviceId) {
return $argument instanceof Uuid && $argument == Uuid::import($publicDeviceId);
}))->once()->andReturn($device)
->shouldReceive('delete')->once()->with($deviceId)->andReturn($wasDeleteSuccessful);
 
$this->app->instance(IDeviceRepository::class, $mockDeviceRepository);
 
$response = $this->actingAs($mockUser)->get("/devices/delete/$publicDeviceId");
 
return $response;
}
 
private function callUpdateOnDeviceUserDoesNotOwn(User $user, Device $device): TestResponse
{
$mockDeviceRepository = Mockery::mock(IDeviceRepository::class);
$mockDeviceRepository
->shouldReceive('getForPublicId')->with(Mockery::on(function (Uuid $argument) use ($device) {
return $argument instanceof Uuid && $argument == Uuid::import($device->public_id);
}))->once()->andReturn($device)
->shouldReceive('update')->never();
 
$this->app->instance(IDeviceRepository::class, $mockDeviceRepository);
 
return $this->putToUpdateWithUserAndDeviceId($user, $device->public_id);
}
 
private function callUpdateOnDeviceUserOwns(User $user, Device $device): TestResponse
{
$mockDeviceRepository = $this->givenActionCalledOnDeviceRepository($device, 'update');
Line exceeds 120 characters; contains 122 characters
$mockDeviceRepository->shouldReceive('getForPublicId')->with(Mockery::on(function (Uuid $argument) use ($device) {
return $argument instanceof Uuid && $argument == Uuid::import($device->public_id);
}))->once()->andReturn($device);
 
return $this->putToUpdateWithUserAndDeviceId($user, $device->public_id);
}
 
private function putToUpdateWithUserAndDeviceId(User $user, string $publicDeviceId): TestResponse
{
$csrfToken = self::$faker->uuid();
 
return $this->actingAs($user)->withSession(['_token' => $csrfToken])
->put("/devices/update/$publicDeviceId", [
'name' => self::$faker->word(),
'description' => self::$faker->sentence(),
'on_code' => self::$faker->randomNumber(),
'off_code' => self::$faker->randomNumber(),
'pulse_length' => self::$faker->randomNumber(),
'_token' => $csrfToken
]);
}
 
private function callControl(User $user, string $action, string $publicDeviceId): TestResponse
{
$csrfToken = self::$faker->uuid();
 
return $this->actingAs($user)->withSession(['_token' => $csrfToken])
->post("/devices/$action/$publicDeviceId", [
'_token' => $csrfToken
]);
}
 
protected function makeUser(): User
{
return factory(User::class)->make([
'id' => self::$faker->randomNumber()
]);
}
 
private function makeDevice(): Device
{
return factory(Device::class)->make([
'id' => self::$faker->randomNumber()
]);
}
 
private function mockUserOwnsDevice(int $deviceId, bool $userOwnsDevice): User
{
$mockUser = Mockery::mock(User::class);
$mockUser->shouldReceive('ownsDevice')->with($deviceId)->once()->andReturn($userOwnsDevice);
 
return $mockUser;
}
 
private function givenActionCalledOnDeviceRepository(Device $device, string $action): IDeviceRepository
{
$mockDeviceRepository = Mockery::mock(IDeviceRepository::class);
$mockDeviceRepository->shouldReceive($action)->once()->andReturn($device);
 
return $this->app->instance(IDeviceRepository::class, $mockDeviceRepository);
}
 
private function givenGetForPublicIdCalledForDevice(Device $device): void
{
$mockDeviceRepository = Mockery::mock(IDeviceRepository::class);
Line exceeds 120 characters; contains 122 characters
$mockDeviceRepository->shouldReceive('getForPublicId')->with(Mockery::on(function (Uuid $argument) use ($device) {
return $argument instanceof Uuid && $argument == Uuid::import($device->public_id);
}))->once()->andReturn($device);
 
$this->app->instance(IDeviceRepository::class, $mockDeviceRepository);
}
}