Skip to content

Commit ff7ca53

Browse files
author
alutskevich
committed
Step 4: create unit test for service and add validation for new rover coordinates
1 parent e723152 commit ff7ca53

File tree

5 files changed

+251
-29
lines changed

5 files changed

+251
-29
lines changed

src/Application/Infrastructure/RoverRepository.php

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,33 +9,39 @@
99

1010
final class RoverRepository implements RoverRepositoryInterface
1111
{
12-
public function turnLeft(Rover $rover): void
12+
public function turnLeft(Rover $rover): Rover
1313
{
1414
match ($rover->getDirection()) {
1515
Direction::North => $rover->setDirection(Direction::West),
1616
Direction::South => $rover->setDirection(Direction::East),
1717
Direction::West => $rover->setDirection(Direction::South),
1818
Direction::East => $rover->setDirection(Direction::North),
1919
};
20+
21+
return $rover;
2022
}
2123

22-
public function turnRight(Rover $rover): void
24+
public function turnRight(Rover $rover): Rover
2325
{
2426
match ($rover->getDirection()) {
2527
Direction::North => $rover->setDirection(Direction::East),
2628
Direction::South => $rover->setDirection(Direction::West),
2729
Direction::West => $rover->setDirection(Direction::North),
2830
Direction::East => $rover->setDirection(Direction::South),
2931
};
32+
33+
return $rover;
3034
}
3135

32-
public function move(Rover $rover): void
36+
public function move(Rover $rover): Rover
3337
{
3438
match ($rover->getDirection()) {
3539
Direction::North => $rover->setCoordinateY($rover->getCoordinateY() + 1),
3640
Direction::South => $rover->setCoordinateY($rover->getCoordinateY() - 1),
3741
Direction::West => $rover->setCoordinateX($rover->getCoordinateX() - 1),
3842
Direction::East => $rover->setCoordinateX($rover->getCoordinateX() + 1),
3943
};
44+
45+
return $rover;
4046
}
4147
}

src/Application/Repository/RoverRepositoryInterface.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77

88
interface RoverRepositoryInterface
99
{
10-
public function turnLeft(Rover $rover): void;
10+
public function turnLeft(Rover $rover): Rover;
1111

12-
public function turnRight(Rover $rover): void;
12+
public function turnRight(Rover $rover): Rover;
1313

14-
public function move(Rover $rover): void;
14+
public function move(Rover $rover): Rover;
1515
}

src/Application/Service/NasaRoverService.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public function process(RoverInformationDto $roverInformation): RoverPositionDto
2828
);
2929

3030
foreach(\str_split($roverInformation->getActions()) as $action) {
31-
match($action) {
31+
$rover = match($action) {
3232
'M' => $this->roverRepository->move($rover),
3333
'L' => $this->roverRepository->turnLeft($rover),
3434
'R' => $this->roverRepository->turnRight($rover),

src/Command/NasaRoversCommand.php

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@
77
use App\Application\Assertions\TopRightCoordinatesIsValidAssertion;
88
use App\Application\Enum\Direction;
99
use App\Application\Exceptions\InvalidRoverInformationException;
10-
use App\Application\Repository\RoverRepositoryInterface;
1110
use App\Application\Service\NasaRoverService;
1211
use App\Command\NasaRovers\Dto\BorderCoordinatesDto;
1312
use App\Command\NasaRovers\Dto\RoverInformationDto;
13+
use Exception;
1414
use InvalidArgumentException;
1515
use Symfony\Component\Console\Command\Command;
1616
use Symfony\Component\Console\Helper\QuestionHelper;
@@ -29,7 +29,6 @@ class NasaRoversCommand extends Command
2929
private QuestionHelper $questionHelper;
3030

3131
public function __construct(
32-
private RoverRepositoryInterface $roverRepository,
3332
private TopRightCoordinatesIsValidAssertion $topRightCoordinatesIsValidAssertion,
3433
private RoverCoordinatesIsValidAssertion $roverCoordinatesIsValidAssertion,
3534
private NasaRoverService $nasaRoverService,
@@ -56,6 +55,9 @@ protected function configure(): void
5655
);
5756
}
5857

58+
/**
59+
* @throws Exception
60+
*/
5961
protected function execute(
6062
InputInterface $input,
6163
OutputInterface $output
@@ -85,7 +87,7 @@ protected function execute(
8587
$roversInformation = $this->getRoversInformation($input, $output);
8688

8789
try {
88-
$rovers = $this->setupRovers($borderCoordinatesDto, $output, $roversInformation);
90+
$rovers = $this->setupRovers($borderCoordinatesDto, $roversInformation);
8991
} catch (InvalidRoverInformationException $exception) {
9092
$output->write(
9193
\sprintf(
@@ -105,8 +107,28 @@ protected function execute(
105107
}
106108

107109
foreach ($rovers as $roverInformation) {
110+
$output->write('Moving rover...');
108111
$rover = $this->nasaRoverService->process($roverInformation);
109112

113+
try {
114+
$this->roverCoordinatesIsValidAssertion->assert(
115+
$borderCoordinatesDto,
116+
$rover->getXCoordinate(),
117+
$rover->getYCoordinate()
118+
);
119+
} catch (InvalidArgumentException) {
120+
$output->writeln([
121+
"\n",
122+
\sprintf(
123+
'Rover left Mars: %d %d %s',
124+
$rover->getXCoordinate(),
125+
$rover->getYCoordinate(),
126+
$rover->getDirection()->toString()
127+
),
128+
"\n"
129+
]);
130+
}
131+
110132
$output->writeln([
111133
"\n",
112134
\sprintf(
@@ -140,24 +162,6 @@ private function getTopRightCoordinates(
140162
return $this->questionHelper->ask($input, $output, $question);
141163
}
142164

143-
private function getRoverCoordinates(
144-
InputInterface $input,
145-
OutputInterface $output,
146-
): string {
147-
$question = new Question('Enter start coordinates of the rover (X Y Direction). For example, 1 1 N: ');
148-
$question->setValidator(function ($answer) {
149-
if (!is_string($answer) || empty($answer) || 1 !== preg_match('/^[0-9]{1,} [0-9]{1,} [NSWE]$/', $answer)) {
150-
throw new \RuntimeException(
151-
'Coordinates should be like X Y Direction. For example, 1 1 N'
152-
);
153-
}
154-
155-
return $answer;
156-
});
157-
158-
return $this->questionHelper->ask($input, $output, $question);
159-
}
160-
161165
/**
162166
* @return string[]
163167
*/
@@ -187,7 +191,6 @@ private function getRoversInformation(InputInterface $input, OutputInterface $ou
187191
*/
188192
private function setupRovers(
189193
BorderCoordinatesDto $borderCoordinatesDto,
190-
OutputInterface $output,
191194
array $roversInformation
192195
): array {
193196
/** @var RoverInformationDto[] $rovers */
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace App\Tests\Unit\Application\Service;
5+
6+
use App\Application\Enum\Direction;
7+
use App\Application\Factory\RoverFactory;
8+
use App\Application\Repository\RoverRepositoryInterface;
9+
use App\Application\Service\NasaRoverService;
10+
use App\Application\Service\RoverPositionDto;
11+
use App\Command\NasaRovers\Dto\RoverInformationDto;
12+
use App\Domain\Entity\Rover;
13+
use Exception;
14+
use PHPUnit\Framework\MockObject\MockObject;
15+
use PHPUnit\Framework\TestCase;
16+
17+
class NasaRoverServiceTest extends TestCase
18+
{
19+
private RoverFactory|MockObject $roverFactory;
20+
private RoverRepositoryInterface|MockObject $roverRepository;
21+
private NasaRoverService $nasaRoverService;
22+
23+
protected function setUp(): void
24+
{
25+
$this->roverFactory = $this->createMock(RoverFactory::class);
26+
$this->roverRepository = $this->createMock(RoverRepositoryInterface::class);
27+
28+
$this->nasaRoverService = new NasaRoverService(
29+
$this->roverFactory,
30+
$this->roverRepository
31+
);
32+
33+
parent::setUp();
34+
}
35+
36+
/**
37+
* @throws Exception
38+
*
39+
* @dataProvider roverCoordinatesDataProvider
40+
*/
41+
public function testSuccessRoverMoving(
42+
int $xCoordinate,
43+
int $yCoordinate,
44+
Direction $direction
45+
): void {
46+
$rover = new Rover($xCoordinate, $yCoordinate, $direction);
47+
$rover2 = new Rover($xCoordinate, $yCoordinate+1, $direction);
48+
$finalRoverObject = new Rover($xCoordinate, $yCoordinate+2, $direction);
49+
50+
$this->roverFactory->expects(self::once())
51+
->method('create')
52+
->with($xCoordinate, $yCoordinate, $direction)
53+
->willReturn($rover);
54+
55+
$this->roverRepository->expects(self::exactly(2))
56+
->method('move')
57+
->withConsecutive(
58+
[$rover],
59+
[$rover2]
60+
)
61+
->willReturnOnConsecutiveCalls(
62+
$rover2,
63+
$finalRoverObject
64+
);
65+
66+
$this->roverRepository->expects(self::never())
67+
->method('turnLeft');
68+
69+
$this->roverRepository->expects(self::never())
70+
->method('turnRight');
71+
72+
$expectedResult = new RoverPositionDto($xCoordinate, $yCoordinate+2, Direction::North);
73+
74+
$roverInformationDto = new RoverInformationDto($xCoordinate, $yCoordinate, $direction);
75+
$roverInformationDto->setActions('MM');
76+
$actualResult = $this->nasaRoverService->process($roverInformationDto);
77+
78+
$this->assertEquals($expectedResult, $actualResult);
79+
}
80+
81+
/**
82+
* @throws Exception
83+
*
84+
* @dataProvider roverCoordinatesDataProvider
85+
*/
86+
public function testSuccessTurnLeft(
87+
int $xCoordinate,
88+
int $yCoordinate
89+
): void {
90+
$direction = Direction::North;
91+
92+
$rover = new Rover($xCoordinate, $yCoordinate, $direction);
93+
$rover2 = new Rover($xCoordinate, $yCoordinate+1, Direction::West);
94+
$finalRoverObject = new Rover($xCoordinate, $yCoordinate+2, Direction::South);
95+
96+
$this->roverFactory->expects(self::once())
97+
->method('create')
98+
->with($xCoordinate, $yCoordinate, $direction)
99+
->willReturn($rover);
100+
101+
$this->roverRepository->expects(self::exactly(2))
102+
->method('turnLeft')
103+
->withConsecutive(
104+
[$rover],
105+
[$rover2]
106+
)
107+
->willReturnOnConsecutiveCalls(
108+
$rover2,
109+
$finalRoverObject
110+
);
111+
112+
$this->roverRepository->expects(self::never())
113+
->method('move');
114+
115+
$this->roverRepository->expects(self::never())
116+
->method('turnRight');
117+
118+
$expectedResult = new RoverPositionDto($xCoordinate, $yCoordinate+2, Direction::South);
119+
120+
$roverInformationDto = new RoverInformationDto($xCoordinate, $yCoordinate, $direction);
121+
$roverInformationDto->setActions('LL');
122+
$actualResult = $this->nasaRoverService->process($roverInformationDto);
123+
124+
$this->assertEquals($expectedResult, $actualResult);
125+
}
126+
127+
/**
128+
* @throws Exception
129+
*
130+
* @dataProvider roverCoordinatesDataProvider
131+
*/
132+
public function testSuccessTurnRight(
133+
int $xCoordinate,
134+
int $yCoordinate
135+
): void {
136+
$direction = Direction::North;
137+
138+
$rover = new Rover($xCoordinate, $yCoordinate, $direction);
139+
$rover2 = new Rover($xCoordinate, $yCoordinate+1, Direction::East);
140+
$finalRoverObject = new Rover($xCoordinate, $yCoordinate+2, Direction::South);
141+
142+
$this->roverFactory->expects(self::once())
143+
->method('create')
144+
->with($xCoordinate, $yCoordinate, $direction)
145+
->willReturn($rover);
146+
147+
$this->roverRepository->expects(self::exactly(2))
148+
->method('turnRight')
149+
->withConsecutive(
150+
[$rover],
151+
[$rover2]
152+
)
153+
->willReturnOnConsecutiveCalls(
154+
$rover2,
155+
$finalRoverObject
156+
);
157+
158+
$this->roverRepository->expects(self::never())
159+
->method('move');
160+
161+
$this->roverRepository->expects(self::never())
162+
->method('turnLeft');
163+
164+
$expectedResult = new RoverPositionDto($xCoordinate, $yCoordinate+2, Direction::South);
165+
166+
$roverInformationDto = new RoverInformationDto($xCoordinate, $yCoordinate, $direction);
167+
$roverInformationDto->setActions('RR');
168+
$actualResult = $this->nasaRoverService->process($roverInformationDto);
169+
170+
$this->assertEquals($expectedResult, $actualResult);
171+
}
172+
173+
/**
174+
* @throws Exception
175+
*
176+
* @dataProvider roverCoordinatesDataProvider
177+
*/
178+
public function testInvalidAction(
179+
int $xCoordinate,
180+
int $yCoordinate,
181+
Direction $direction
182+
): void {
183+
$rover = new Rover($xCoordinate, $yCoordinate, $direction);
184+
185+
$this->roverFactory->expects(self::once())
186+
->method('create')
187+
->with($xCoordinate, $yCoordinate, $direction)
188+
->willReturn($rover);
189+
190+
$this->roverRepository->expects(self::never())
191+
->method('turnRight');
192+
193+
$this->roverRepository->expects(self::never())
194+
->method('move');
195+
196+
$this->roverRepository->expects(self::never())
197+
->method('turnLeft');
198+
199+
$roverInformationDto = new RoverInformationDto($xCoordinate, $yCoordinate, $direction);
200+
$roverInformationDto->setActions('DD');
201+
202+
$this->expectException(Exception::class);
203+
204+
$this->nasaRoverService->process($roverInformationDto);
205+
}
206+
207+
public function roverCoordinatesDataProvider(): array
208+
{
209+
return [
210+
[1, 2, Direction::North]
211+
];
212+
}
213+
}

0 commit comments

Comments
 (0)