Skip to content

Commit 2fb7de1

Browse files
committed
feat: add new Pest ToolResult expectations for cleaner testing
1 parent 190b016 commit 2fb7de1

File tree

2 files changed

+56
-78
lines changed

2 files changed

+56
-78
lines changed

tests/Feature/Mcp/Tools/ListRoutesTest.php

Lines changed: 33 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
use Illuminate\Support\Facades\Route;
66
use Laravel\Boost\Mcp\Tools\ListRoutes;
7-
use Laravel\Mcp\Server\Tools\ToolResult;
87

98
beforeEach(function () {
109
Route::get('/admin/dashboard', function () {
@@ -36,90 +35,69 @@
3635
$tool = new ListRoutes;
3736
$result = $tool->handle([]);
3837

39-
expect($result)->toBeInstanceOf(ToolResult::class);
40-
$data = $result->toArray();
41-
expect($data['isError'])->toBeFalse()
42-
->and($data['content'][0]['text'])->toBeString()
43-
->and($data['content'][0]['text'])->toContain('GET|HEAD')
44-
->and($data['content'][0]['text'])->toContain('admin.dashboard')
45-
->and($data['content'][0]['text'])->toContain('user.profile');
38+
expect($result)->isToolResult()
39+
->toolHasNoError()
40+
->toolTextContains('GET|HEAD', 'admin.dashboard', 'user.profile');
4641
});
4742

4843
test('it sanitizes name parameter wildcards and filters correctly', function () {
4944
$tool = new ListRoutes;
5045

5146
$result = $tool->handle(['name' => '*admin*']);
52-
$output = $result->toArray()['content'][0]['text'];
5347

54-
expect($result)->toBeInstanceOf(ToolResult::class)
55-
->and($result->toArray()['isError'])->toBeFalse()
56-
->and($output)->toContain('admin.dashboard')
57-
->and($output)->toContain('admin.users.store')
58-
->and($output)->not->toContain('user.profile')
59-
->and($output)->not->toContain('two-factor.enable');
48+
expect($result)->isToolResult()
49+
->toolHasNoError()
50+
->toolTextContains('admin.dashboard', 'admin.users.store')
51+
->and($result)->not->toolTextContains('user.profile', 'two-factor.enable');
6052

6153
$result = $tool->handle(['name' => '*two-factor*']);
62-
$output = $result->toArray()['content'][0]['text'];
6354

64-
expect($output)->toContain('two-factor.enable')
65-
->and($output)->not->toContain('admin.dashboard')
66-
->and($output)->not->toContain('user.profile');
55+
expect($result)->toolTextContains('two-factor.enable')
56+
->and($result)->not->toolTextContains('admin.dashboard', 'user.profile');
6757

6858
$result = $tool->handle(['name' => '*api*']);
69-
$output = $result->toArray()['content'][0]['text'];
7059

71-
expect($output)->toContain('api.posts.index')
72-
->and($output)->toContain('api.posts.update')
73-
->and($output)->not->toContain('admin.dashboard')
74-
->and($output)->not->toContain('user.profile');
60+
expect($result)->toolTextContains('api.posts.index', 'api.posts.update')
61+
->and($result)->not->toolTextContains('admin.dashboard', 'user.profile');
62+
7563
});
7664

7765
test('it sanitizes method parameter wildcards and filters correctly', function () {
7866
$tool = new ListRoutes;
7967

8068
$result = $tool->handle(['method' => 'GET*POST']);
81-
$output = $result->toArray()['content'][0]['text'];
8269

83-
expect($result->toArray()['isError'])->toBeFalse()
84-
->and($output)->toContain('ERROR Your application doesn\'t have any routes matching the given criteria.');
70+
expect($result)->isToolResult()
71+
->toolHasNoError()
72+
->toolTextContains('ERROR Your application doesn\'t have any routes matching the given criteria.');
8573

8674
$result = $tool->handle(['method' => '*GET*']);
87-
$output = $result->toArray()['content'][0]['text'];
8875

89-
expect($output)->toContain('admin.dashboard')
90-
->and($output)->toContain('user.profile')
91-
->and($output)->toContain('api.posts.index')
92-
->and($output)->not->toContain('admin.users.store');
76+
expect($result)->toolTextContains('admin.dashboard', 'user.profile', 'api.posts.index')
77+
->and($result)->not->toolTextContains('admin.users.store');
9378

9479
$result = $tool->handle(['method' => '*POST*']);
95-
$output = $result->toArray()['content'][0]['text'];
9680

97-
expect($output)->toContain('admin.users.store')
98-
->and($output)->not->toContain('admin.dashboard');
81+
expect($result)->toolTextContains('admin.users.store')
82+
->and($result)->not->toolTextContains('admin.dashboard');
9983
});
10084

10185
test('it handles edge cases and empty results correctly', function () {
10286
$tool = new ListRoutes;
10387

10488
$result = $tool->handle(['name' => '*']);
105-
expect($result)->toBeInstanceOf(ToolResult::class)
106-
->and($result->toArray()['isError'])->toBeFalse();
10789

108-
$output = $result->toArray()['content'][0]['text'];
109-
expect($output)->toContain('admin.dashboard')
110-
->and($output)->toContain('user.profile')
111-
->and($output)->toContain('two-factor.enable');
90+
expect($result)->isToolResult()
91+
->toolHasNoError()
92+
->toolTextContains('admin.dashboard', 'user.profile', 'two-factor.enable');
11293

11394
$result = $tool->handle(['name' => '*nonexistent*']);
114-
$output = $result->toArray()['content'][0]['text'];
11595

116-
expect($output)->toContain('ERROR Your application doesn\'t have any routes matching the given criteria.');
96+
expect($result)->toolTextContains('ERROR Your application doesn\'t have any routes matching the given criteria.');
11797

11898
$result = $tool->handle(['name' => '']);
119-
$output = $result->toArray()['content'][0]['text'];
12099

121-
expect($output)->toContain('admin.dashboard')
122-
->and($output)->toContain('user.profile');
100+
expect($result)->toolTextContains('admin.dashboard', 'user.profile');
123101
});
124102

125103
test('it handles multiple parameters with wildcard sanitization', function () {
@@ -130,38 +108,25 @@
130108
'method' => '*GET*',
131109
]);
132110

133-
$output = $result->toArray()['content'][0]['text'];
134-
135-
expect($result->toArray()['isError'])->toBeFalse()
136-
->and($output)->toContain('admin.dashboard')
137-
->and($output)->not->toContain('admin.users.store')
138-
->and($output)->not->toContain('user.profile');
111+
expect($result)->isToolResult()
112+
->toolHasNoError()
113+
->toolTextContains('admin.dashboard')
114+
->and($result)->not->toolTextContains('admin.users.store', 'user.profile');
139115

140116
$result = $tool->handle([
141117
'name' => '*user*',
142118
'method' => '*POST*',
143119
]);
144120

145-
$output = $result->toArray()['content'][0]['text'];
146-
147-
if (str_contains($output, 'admin.users.store')) {
148-
expect($output)->toContain('admin.users.store');
149-
} else {
150-
expect($output)->toContain('ERROR Your application doesn\'t have any routes matching the given criteria.');
151-
}
121+
expect($result)->toolTextContains('admin.users.store');
152122
});
153123

154124
test('it handles the original problematic wildcard case', function () {
155125
$tool = new ListRoutes;
156126

157-
$result = $tool->handle(['name' => '*/two-factor/']);
158-
expect($result)->toBeInstanceOf(ToolResult::class)
159-
->and($result->toArray()['isError'])->toBeFalse();
127+
$result = $tool->handle(['name' => '*two-factor*']);
160128

161-
$output = $result->toArray()['content'][0]['text'];
162-
if (str_contains($output, 'two-factor.enable')) {
163-
expect($output)->toContain('two-factor.enable');
164-
} else {
165-
expect($output)->toContain('ERROR');
166-
}
129+
expect($result)->isToolResult()
130+
->toolHasNoError()
131+
->toolTextContains('two-factor.enable');
167132
});

tests/Pest.php

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,29 @@
1515

1616
uses(Tests\TestCase::class)->in('Feature');
1717

18-
/*
19-
|--------------------------------------------------------------------------
20-
| Expectations
21-
|--------------------------------------------------------------------------
22-
|
23-
| When you're writing tests, you often need to check that values meet certain conditions. The
24-
| "expect()" function gives you access to a set of "expectations" methods that you can use
25-
| to assert different things. Of course, you may extend the Expectation API at any time.
26-
|
27-
*/
18+
expect()->extend('isToolResult', function () {
19+
return $this->toBeInstanceOf(\Laravel\Mcp\Server\Tools\ToolResult::class);
20+
});
21+
22+
expect()->extend('toolTextContains', function (mixed ...$needles) {
23+
/** @var \Laravel\Mcp\Server\Tools\ToolResult $this->value */
24+
$output = implode('', array_column($this->value->toArray()['content'], 'text'));
25+
expect($output)->toContain(...func_get_args());
26+
27+
return $this;
28+
});
29+
30+
expect()->extend('toolHasError', function () {
31+
expect($this->value->toArray()['isError'])->toBeTrue();
32+
33+
return $this;
34+
});
35+
36+
expect()->extend('toolHasNoError', function () {
37+
expect($this->value->toArray()['isError'])->toBeFalse();
38+
39+
return $this;
40+
});
2841

2942
function fixture(string $name): string
3043
{

0 commit comments

Comments
 (0)