Skip to content

Commit 32eaf4f

Browse files
committedJan 22, 2025·
minor #6753 Improve tests (javiereguiluz)
This PR was squashed before being merged into the 4.x branch. Discussion ---------- Improve tests Ths is the first step in the goal to remove as many mocks as possible and to never mock a final class (which requires an ugly hack that I Want to remove too). Commits ------- 8e7a0ec Improve tests
2 parents f0ae222 + 8e7a0ec commit 32eaf4f

File tree

5 files changed

+97
-103
lines changed

5 files changed

+97
-103
lines changed
 

‎src/Twig/Component/Icon.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
namespace EasyCorp\Bundle\EasyAdminBundle\Twig\Component;
44

55
use EasyCorp\Bundle\EasyAdminBundle\Config\Option\IconSet;
6+
use EasyCorp\Bundle\EasyAdminBundle\Contracts\Provider\AdminContextProviderInterface;
67
use EasyCorp\Bundle\EasyAdminBundle\Dto\IconDto;
7-
use EasyCorp\Bundle\EasyAdminBundle\Provider\AdminContextProvider;
88

99
class Icon
1010
{
@@ -13,7 +13,7 @@ class Icon
1313
private ?string $iconSet = null;
1414

1515
public function __construct(
16-
private AdminContextProvider $adminContextProvider,
16+
private AdminContextProviderInterface $adminContextProvider,
1717
) {
1818
}
1919

‎src/Twig/EasyAdminTwigExtension.php

+12-2
Original file line numberDiff line numberDiff line change
@@ -179,10 +179,20 @@ public function representAsString($value, string|callable|null $toStringMethod =
179179
}
180180

181181
if (method_exists($value, 'getId')) {
182-
return sprintf('%s #%s', $value::class, $value->getId());
182+
return sprintf(
183+
'%s #%s',
184+
// remove null bytes from class name (this happens in anonymous classes)
185+
str_replace("\0", '', $value::class),
186+
$value->getId()
187+
);
183188
}
184189

185-
return sprintf('%s #%s', $value::class, hash('xxh32', (string) spl_object_id($value)));
190+
return sprintf(
191+
'%s #%s',
192+
// remove null bytes from class name (this happens in anonymous classes)
193+
str_replace("\0", '', $value::class),
194+
hash('xxh32', (string) spl_object_id($value))
195+
);
186196
}
187197

188198
return '';

‎tests/Menu/MenuItemMatcherTest.php

+53-66
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,13 @@
99
use EasyCorp\Bundle\EasyAdminBundle\Router\AdminRouteGenerator;
1010
use EasyCorp\Bundle\EasyAdminBundle\Router\AdminUrlGenerator;
1111
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
12-
use Symfony\Component\HttpFoundation\InputBag;
1312
use Symfony\Component\HttpFoundation\Request;
1413

1514
class MenuItemMatcherTest extends KernelTestCase
1615
{
1716
public function testIsSelectedWhenContextIsNull()
1817
{
19-
$request = $this->getRequestMock();
18+
$request = $this->createRequest();
2019

2120
self::bootKernel();
2221
$adminUrlGenerator = self::getContainer()->get(AdminUrlGenerator::class);
@@ -31,7 +30,7 @@ public function testIsSelectedWhenContextIsNull()
3130

3231
public function testIsSelectedWhenMenuItemIsSection()
3332
{
34-
$request = $this->getRequestMock();
33+
$request = $this->createRequest();
3534

3635
self::bootKernel();
3736
$adminUrlGenerator = self::getContainer()->get(AdminUrlGenerator::class);
@@ -47,8 +46,8 @@ public function testIsSelectedWhenMenuItemIsSection()
4746

4847
public function testIsSelectedWithCrudControllers()
4948
{
50-
$request = $this->getRequestMock(
51-
getControllerFqcn: 'App\Controller\Admin\SomeController',
49+
$request = $this->createRequest(
50+
crudControllerFqcn: 'App\Controller\Admin\SomeController',
5251
);
5352

5453
self::bootKernel();
@@ -68,10 +67,10 @@ public function testIsSelectedWithCrudControllers()
6867
$menuItemMatcher->markSelectedMenuItem([$menuItemDto], $request);
6968
$this->assertTrue($menuItemDto->isSelected(), 'The CRUD controller matches');
7069

71-
$request = $this->getRequestMock(
72-
getControllerFqcn: 'App\Controller\Admin\SomeController',
73-
getPrimaryKeyValue: '57',
74-
getCurrentAction: 'edit',
70+
$request = $this->createRequest(
71+
crudControllerFqcn: 'App\Controller\Admin\SomeController',
72+
entityId: '57',
73+
action: 'edit',
7574
);
7675

7776
$menuItemDto = $this->getMenuItemDto(crudControllerFqcn: 'App\Controller\Admin\SomeController', action: 'edit', entityId: '57');
@@ -82,10 +81,10 @@ public function testIsSelectedWithCrudControllers()
8281
$menuItemMatcher->markSelectedMenuItem([$menuItemDto], $request);
8382
$this->assertFalse($menuItemDto->isSelected(), 'The entity ID of the menu item does not match');
8483

85-
$request = $this->getRequestMock(
86-
getControllerFqcn: 'App\Controller\Admin\SomeController',
87-
getPrimaryKeyValue: '57',
88-
getCurrentAction: 'detail',
84+
$request = $this->createRequest(
85+
crudControllerFqcn: 'App\Controller\Admin\SomeController',
86+
entityId: '57',
87+
action: 'detail',
8988
);
9089

9190
$menuItemDto = $this->getMenuItemDto(crudControllerFqcn: 'App\Controller\Admin\SomeController', action: Crud::PAGE_DETAIL, entityId: '57');
@@ -99,7 +98,7 @@ public function testIsSelectedWithCrudControllers()
9998

10099
public function testIsSelectedWithRoutes()
101100
{
102-
$request = $this->getRequestMock(
101+
$request = $this->createRequest(
103102
routeName: 'some_route',
104103
);
105104

@@ -117,7 +116,7 @@ public function testIsSelectedWithRoutes()
117116
$menuItemMatcher->markSelectedMenuItem([$menuItemDto], $request);
118117
$this->assertFalse($menuItemDto->isSelected());
119118

120-
$request = $this->getRequestMock(
119+
$request = $this->createRequest(
121120
routeName: 'some_route',
122121
routeParameters: ['foo1' => 'bar1', 'foo2' => 'bar2'],
123122
);
@@ -141,8 +140,9 @@ public function testIsSelectedWithRoutes()
141140

142141
public function testIsSelectedWithUrls()
143142
{
144-
$request = $this->getRequestMock(
145-
getUri: 'https://example.com/foo?bar=baz',
143+
$request = $this->createRequest(
144+
requestPath: '/foo',
145+
queryParameters: ['bar' => 'baz'],
146146
);
147147

148148
self::bootKernel();
@@ -182,7 +182,7 @@ public function testMenuWithDashboardItem()
182182
$this->getMenuItemDto(label: 'item2', routeName: 'item2'),
183183
];
184184

185-
$request = $this->getRequestMock(
185+
$request = $this->createRequest(
186186
routeName: 'item2',
187187
);
188188

@@ -198,8 +198,8 @@ public function testMenuWithDashboardItem()
198198
public function testComplexMenu()
199199
{
200200
$menuItems = $this->getComplexMenuItems();
201-
$request = $this->getRequestMock(
202-
getControllerFqcn: 'App\Controller\Admin\Controller1',
201+
$request = $this->createRequest(
202+
crudControllerFqcn: 'App\Controller\Admin\Controller1',
203203
);
204204

205205
self::bootKernel();
@@ -214,9 +214,9 @@ public function testComplexMenu()
214214

215215
unset($menuItems);
216216
$menuItems = $this->getComplexMenuItems();
217-
$request = $this->getRequestMock(
218-
getControllerFqcn: 'App\Controller\Admin\Controller1',
219-
getCurrentAction: 'edit',
217+
$request = $this->createRequest(
218+
crudControllerFqcn: 'App\Controller\Admin\Controller1',
219+
action: 'edit',
220220
// the primary key value is missing on purpose in this example
221221
);
222222
$menuItems = $menuItemMatcher->markSelectedMenuItem($menuItems, $request);
@@ -225,40 +225,40 @@ public function testComplexMenu()
225225

226226
unset($menuItems);
227227
$menuItems = $this->getComplexMenuItems();
228-
$request = $this->getRequestMock(
229-
getControllerFqcn: 'App\Controller\Admin\Controller1',
230-
getCurrentAction: 'new',
228+
$request = $this->createRequest(
229+
crudControllerFqcn: 'App\Controller\Admin\Controller1',
230+
action: 'new',
231231
);
232232
$menuItems = $menuItemMatcher->markSelectedMenuItem($menuItems, $request);
233233
$this->assertSame('item5', $this->getSelectedMenuItemLabel($menuItems), 'Perfect match: CRUD controller and action');
234234
$this->assertSame('item4', $this->getExpandedMenuItemLabel($menuItems), 'A submenu item is matched, so its parent item must be marked as expanded');
235235

236236
unset($menuItems);
237237
$menuItems = $this->getComplexMenuItems();
238-
$request = $this->getRequestMock(
239-
getControllerFqcn: 'App\Controller\Admin\Controller2',
240-
getCurrentAction: 'new',
238+
$request = $this->createRequest(
239+
crudControllerFqcn: 'App\Controller\Admin\Controller2',
240+
action: 'new',
241241
);
242242
$menuItems = $menuItemMatcher->markSelectedMenuItem($menuItems, $request);
243243
$this->assertSame('item3', $this->getSelectedMenuItemLabel($menuItems), 'Approximate match: controller matches, action doesn\'t match; the item with the INDEX action is selected by default');
244244
$this->assertNull($this->getExpandedMenuItemLabel($menuItems), 'No menu item is marked as expanded');
245245

246246
unset($menuItems);
247247
$menuItems = $this->getComplexMenuItems();
248-
$request = $this->getRequestMock(
249-
getControllerFqcn: 'App\Controller\Admin\Controller2',
250-
getCurrentAction: 'edit',
251-
getPrimaryKeyValue: 'NOT_57',
248+
$request = $this->createRequest(
249+
crudControllerFqcn: 'App\Controller\Admin\Controller2',
250+
action: 'edit',
251+
entityId: 'NOT_57',
252252
);
253253
$menuItems = $menuItemMatcher->markSelectedMenuItem($menuItems, $request);
254254
$this->assertNull($this->getSelectedMenuItemLabel($menuItems), 'No match: controller and action match, but query parameters don\'t');
255255
$this->assertNull($this->getExpandedMenuItemLabel($menuItems), 'No menu item is marked as expanded');
256256

257257
unset($menuItems);
258258
$menuItems = $this->getComplexMenuItems();
259-
$request = $this->getRequestMock(
260-
getControllerFqcn: 'App\Controller\Admin\Controller3',
261-
getCurrentAction: 'new',
259+
$request = $this->createRequest(
260+
crudControllerFqcn: 'App\Controller\Admin\Controller3',
261+
action: 'new',
262262
);
263263
$menuItems = $menuItemMatcher->markSelectedMenuItem($menuItems, $request);
264264
$this->assertSame('item7', $this->getSelectedMenuItemLabel($menuItems), 'Approximate match: only the controller matches; the item with the INDEX action is selected');
@@ -356,38 +356,25 @@ private function getMenuItemDto(?string $crudControllerFqcn = null, ?string $act
356356
return $menuItemDto;
357357
}
358358

359-
private function getRequestMock(?string $getControllerFqcn = null, ?string $getPrimaryKeyValue = null, ?string $getCurrentAction = null, ?string $routeName = null, ?array $routeParameters = null, ?string $getUri = null): Request
359+
private function createRequest(?string $crudControllerFqcn = null, ?string $entityId = null, ?string $action = null, ?string $routeName = null, ?array $routeParameters = null, ?string $requestPath = null, array $queryParameters = []): Request
360360
{
361-
$queryParameters = [];
362-
363-
if (null !== $getControllerFqcn) {
364-
$queryParameters[EA::CRUD_CONTROLLER_FQCN] = $getControllerFqcn;
365-
}
366-
if (null !== $getCurrentAction) {
367-
$queryParameters[EA::CRUD_ACTION] = $getCurrentAction;
368-
}
369-
370-
if (null !== $getPrimaryKeyValue) {
371-
$queryParameters[EA::ENTITY_ID] = $getPrimaryKeyValue;
372-
}
373-
374-
if (null !== $routeName) {
375-
$queryParameters[EA::ROUTE_NAME] = $routeName;
376-
}
377-
if (null !== $routeParameters) {
378-
$queryParameters[EA::ROUTE_PARAMS] = $routeParameters;
379-
}
380-
381-
$request = $this->getMockBuilder(Request::class)->getMock();
382-
$request->query = new InputBag($queryParameters);
383-
$request->attributes = new InputBag($queryParameters);
384-
385-
if (null !== $getUri) {
386-
$request->method('getUri')->willReturn($getUri);
387-
} else {
388-
$request->method('getUri')->willReturn('/?'.http_build_query($queryParameters));
361+
$queryParameters[EA::CRUD_CONTROLLER_FQCN] = $crudControllerFqcn;
362+
$queryParameters[EA::CRUD_ACTION] = $action;
363+
$queryParameters[EA::ENTITY_ID] = $entityId;
364+
$queryParameters[EA::ROUTE_NAME] = $routeName;
365+
$queryParameters[EA::ROUTE_PARAMS] = $routeParameters;
366+
367+
$queryParameters = array_filter($queryParameters, static fn ($value) => null !== $value);
368+
369+
$serverParameters = [
370+
'HTTPS' => 'On',
371+
'HTTP_HOST' => 'example.com',
372+
'QUERY_STRING' => http_build_query($queryParameters),
373+
];
374+
if (null !== $requestPath) {
375+
$serverParameters['REQUEST_URI'] = '/'.ltrim($requestPath, '/');
389376
}
390377

391-
return $request;
378+
return new Request(query: $queryParameters, attributes: $queryParameters, server: $serverParameters);
392379
}
393380
}

‎tests/Twig/Component/IconTest.php

+7-6
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
namespace EasyCorp\Bundle\EasyAdminBundle\Tests\Twig\Component;
44

55
use EasyCorp\Bundle\EasyAdminBundle\Config\Option\IconSet;
6-
use EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext;
6+
use EasyCorp\Bundle\EasyAdminBundle\Contracts\Context\AdminContextInterface;
7+
use EasyCorp\Bundle\EasyAdminBundle\Contracts\Provider\AdminContextProviderInterface;
78
use EasyCorp\Bundle\EasyAdminBundle\Dto\AssetsDto;
89
use EasyCorp\Bundle\EasyAdminBundle\Provider\AdminContextProvider;
910
use EasyCorp\Bundle\EasyAdminBundle\Twig\Component\Icon;
@@ -123,11 +124,11 @@ public function testUnknownInternalIcon(): void
123124
*/
124125
private function getAdminContextProviderMock(string $appIconSet)
125126
{
126-
$adminContextProvider = $this->getMockBuilder(AdminContextProvider::class)->disableOriginalConstructor()->getMock();
127-
$adminContext = $this->getMockBuilder(AdminContext::class)->disableOriginalConstructor()->getMock();
128-
$assetsDto = $this->getMockBuilder(AssetsDto::class)->disableOriginalConstructor()->getMock();
129-
$assetsDto->method('getIconSet')->willReturn($appIconSet);
130-
$assetsDto->method('getDefaultIconPrefix')->willReturn(''); // TODO
127+
$adminContextProvider = $this->getMockBuilder(AdminContextProviderInterface::class)->disableOriginalConstructor()->getMock();
128+
$adminContext = $this->getMockBuilder(AdminContextInterface::class)->disableOriginalConstructor()->getMock();
129+
$assetsDto = new AssetsDto();
130+
$assetsDto->setIconSet($appIconSet);
131+
$assetsDto->setDefaultIconPrefix(''); // TODO
131132
$adminContext->method('getAssets')->willReturn($assetsDto);
132133
$adminContextProvider->method('getContext')->willReturn($adminContext);
133134

‎tests/Twig/EasyAdminTwigExtensionTest.php

+23-27
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,10 @@
22

33
namespace EasyCorp\Bundle\EasyAdminBundle\Tests\Twig;
44

5-
use EasyCorp\Bundle\EasyAdminBundle\Provider\AdminContextProvider;
65
use EasyCorp\Bundle\EasyAdminBundle\Twig\EasyAdminTwigExtension;
76
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
8-
use Symfony\Component\AssetMapper\ImportMap\ImportMapRenderer;
9-
use Symfony\Component\DependencyInjection\ServiceLocator;
10-
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
117
use Symfony\Contracts\Translation\TranslatableInterface;
128
use Symfony\Contracts\Translation\TranslatorInterface;
13-
use Symfony\UX\Icons\Twig\UXIconRuntime;
149

1510
class EasyAdminTwigExtensionTest extends KernelTestCase
1611
{
@@ -19,45 +14,46 @@ class EasyAdminTwigExtensionTest extends KernelTestCase
1914
*/
2015
public function testRepresentAsString($value, $expectedValue, bool $assertRegex = false, string|callable|null $toStringMethod = null): void
2116
{
22-
$translator = $this->getMockBuilder(TranslatorInterface::class)->disableOriginalConstructor()->getMock();
23-
$translator->method('trans')->willReturnCallback(fn ($value) => '*'.$value);
17+
$customTranslator = new class implements TranslatorInterface {
18+
public function trans(string $id, array $parameters = [], ?string $domain = null, ?string $locale = null): string
19+
{
20+
return '*'.$id;
21+
}
22+
23+
public function getLocale(): string
24+
{
25+
return 'en';
26+
}
27+
};
2428

25-
$extension = new EasyAdminTwigExtension(
26-
$this->getMockBuilder(ServiceLocator::class)->disableOriginalConstructor()->getMock(),
27-
$this->getMockBuilder(AdminContextProvider::class)->disableOriginalConstructor()->getMock(),
28-
$this->getMockBuilder(CsrfTokenManagerInterface::class)->disableOriginalConstructor()->getMock(),
29-
$this->getMockBuilder(ImportMapRenderer::class)->disableOriginalConstructor()->getMock(),
30-
$translator,
31-
$this->getMockBuilder(UXIconRuntime::class)->disableOriginalConstructor()->getMock(),
32-
);
29+
$reflectedClass = new \ReflectionClass(EasyAdminTwigExtension::class);
30+
$twigExtensionInstance = $reflectedClass->newInstanceWithoutConstructor();
31+
$property = $reflectedClass->getProperty('translator');
32+
$property->setValue($twigExtensionInstance, $customTranslator);
3333

34-
$result = $extension->representAsString($value, $toStringMethod);
34+
$result = $twigExtensionInstance->representAsString($value, $toStringMethod);
3535

3636
if ($assertRegex) {
3737
$this->assertMatchesRegularExpression($expectedValue, $result);
3838
} else {
3939
$this->assertSame($expectedValue, $result);
4040
}
41+
42+
$this->assertStringNotContainsString("\0", $result, 'The string representation of a value must not contain the null character (which can happen when the original value is an anonymous class object)');
4143
}
4244

43-
public function testRepresentAsStringExcepion()
45+
public function testRepresentAsStringException(): void
4446
{
4547
$this->expectException(\RuntimeException::class);
4648
$this->expectExceptionMessageMatches('/The method "someMethod\(\)" does not exist or is not callable in the value of type "class@anonymous.*"/');
4749

48-
$extension = new EasyAdminTwigExtension(
49-
$this->getMockBuilder(ServiceLocator::class)->disableOriginalConstructor()->getMock(),
50-
$this->getMockBuilder(AdminContextProvider::class)->disableOriginalConstructor()->getMock(),
51-
$this->getMockBuilder(CsrfTokenManagerInterface::class)->disableOriginalConstructor()->getMock(),
52-
$this->getMockBuilder(ImportMapRenderer::class)->disableOriginalConstructor()->getMock(),
53-
$this->getMockBuilder(TranslatorInterface::class)->disableOriginalConstructor()->getMock(),
54-
$this->getMockBuilder(UXIconRuntime::class)->disableOriginalConstructor()->getMock(),
55-
);
50+
$reflectedClass = new \ReflectionClass(EasyAdminTwigExtension::class);
51+
$twigExtensionInstance = $reflectedClass->newInstanceWithoutConstructor();
5652

57-
$extension->representAsString(new class {}, 'someMethod');
53+
$twigExtensionInstance->representAsString(new class {}, 'someMethod');
5854
}
5955

60-
public function provideValuesForRepresentAsString()
56+
public function provideValuesForRepresentAsString(): iterable
6157
{
6258
yield [null, ''];
6359
yield ['foo bar', 'foo bar'];

0 commit comments

Comments
 (0)
Please sign in to comment.