Skip to content

Commit 16ad717

Browse files
ichinyaroxblnfk
andauthored
docs: add third-party Laravel integration in English and Russian (#7)
Co-authored-by: roxblnfk <roxblnfk@ya.ru>
1 parent 4ef67b8 commit 16ad717

3 files changed

Lines changed: 526 additions & 0 deletions

File tree

‎.vitepress/config.mts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ gtag('config', 'G-VYGDN3X0PR');`],
126126
text: 'Integrations',
127127
items: [
128128
{ text: 'Infection', link: '/docs/bridge/infection.md' },
129+
{ text: 'Laravel (Laratesto)', link: '/docs/bridge/laravel.md' },
129130
{ text: 'Mockery', link: '/docs/bridge/mockery.md' },
130131
{ text: 'Rector', link: '/docs/bridge/rector.md' },
131132
{ text: 'Revolt', link: '/docs/bridge/revolt.md' },
@@ -208,6 +209,7 @@ gtag('config', 'G-VYGDN3X0PR');`],
208209
text: 'Интеграции',
209210
items: [
210211
{ text: 'Infection', link: '/ru/docs/bridge/infection.md' },
212+
{ text: 'Laravel (Laratesto)', link: '/ru/docs/bridge/laravel.md' },
211213
{ text: 'Mockery', link: '/ru/docs/bridge/mockery.md' },
212214
{ text: 'Rector', link: '/ru/docs/bridge/rector.md' },
213215
{ text: 'Revolt', link: '/ru/docs/bridge/revolt.md' },

‎docs/bridge/laravel.md‎

Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
---
2+
outline: [2, 3]
3+
llms: true
4+
llms_description: "How to test Laravel applications with Testo through the third-party ichinya/laratesto package: register LaravelPlugin with LaravelConfig in a suite, write HTTP, database and Artisan tests with LaravelTestCase or the InteractsWithLaravel trait, prepare state in setUpLaravel(), isolate the database with #[RefreshDatabase], #[DatabaseTransactions], #[DatabaseMigrations] or #[DatabaseTruncation], and convert PHPUnit tests with the ichinya/laratesto-rector migrator."
5+
---
6+
7+
# Laravel (Laratesto)
8+
9+
[Laratesto](https://packagist.org/packages/ichinya/laratesto) helps you test Laravel applications with Testo. It boots a fresh Laravel application for every test and brings the helpers you know from Laravel's own testing layer: HTTP requests, authentication, session and database assertions, facades with their fakes, and Artisan commands.
10+
11+
::: info Third-party package
12+
Laratesto is developed and maintained by a third-party developer, [ichinya](https://github.com/ichinya). Report issues and follow releases in [its repository](https://github.com/ichinya/laratesto).
13+
:::
14+
15+
<plugin-info class="\Laratesto\LaravelPlugin" name="Laravel" github="https://github.com/ichinya/laratesto" />
16+
17+
## Installation
18+
19+
Laratesto needs a Laravel 13 application and PHP 8.3 or newer. Run this in the application root:
20+
21+
```bash
22+
composer require --dev ichinya/laratesto
23+
```
24+
25+
Composer pulls in a compatible version of Testo along with it. You can move over step by step: existing PHPUnit or Pest tests keep running on their own runner.
26+
27+
## Setup
28+
29+
Register <class>\Laratesto\LaravelPlugin</class> in the `plugins` section of the Test Suite whose tests need a running Laravel application, usually feature tests. Unit tests are better kept in a separate suite without the plugin, so an application isn't booted for each of them. If Testo isn't configured in the project yet, start with [Getting Started](../intro/getting-started.md#configuration).
30+
31+
```php
32+
use Laratesto\Config\LaravelConfig;
33+
use Laratesto\LaravelPlugin;
34+
use Testo\Application\Config\SuiteConfig;
35+
36+
new SuiteConfig(
37+
name: 'Laravel',
38+
location: ['tests/Feature'],
39+
plugins: [
40+
new LaravelPlugin(new LaravelConfig(
41+
basePath: __DIR__,
42+
environment: 'testing',
43+
config: [
44+
'database.default' => 'sqlite',
45+
'database.connections.sqlite.database' => ':memory:',
46+
'cache.default' => 'array',
47+
'session.driver' => 'array',
48+
],
49+
)),
50+
],
51+
);
52+
```
53+
54+
<signature h="3" name="new \Laratesto\Config\LaravelConfig(string $basePath, string $environment = 'testing', array $config = [])">
55+
<short>Configures how Laratesto boots the Laravel application for each test.</short>
56+
<param name="$basePath">The application root, the directory that contains `bootstrap/app.php`.</param>
57+
<param name="$environment">Becomes `APP_ENV`. Laratesto sets it before the application boots, so Laravel loads `.env.testing` as usual.</param>
58+
<param name="$config">Overrides configuration values. They are applied after the application has booted, so a service provider that reads a setting while booting won't see the override: put such settings in `.env.testing` instead.</param>
59+
</signature>
60+
61+
::: warning Use a throwaway database
62+
The database attributes described below run migrations and truncate tables. Point tests at a database you don't mind losing, like the in-memory SQLite from the example (it needs the PDO SQLite extension).
63+
:::
64+
65+
## Writing tests
66+
67+
### Extending the base class
68+
69+
The easiest way to start is to extend <class>\Laratesto\Testing\LaravelTestCase</class>. Let's add a JSON route to `routes/web.php`:
70+
71+
```php
72+
<?php
73+
74+
use Illuminate\Support\Facades\Route;
75+
76+
Route::get('/laratesto/ping', static fn() => response()->json(['message' => 'pong']));
77+
```
78+
79+
And a test for it in `tests/Feature/HttpTest.php`:
80+
81+
```php
82+
<?php
83+
84+
declare(strict_types=1);
85+
86+
namespace Tests\Feature;
87+
88+
use Laratesto\Testing\LaravelTestCase;
89+
use Testo\Test;
90+
91+
#[Test]
92+
final class HttpTest extends LaravelTestCase
93+
{
94+
public function returnsJson(): void
95+
{
96+
$this->get('/laratesto/ping')
97+
->assertOk()
98+
->assertExactJson(['message' => 'pong']);
99+
}
100+
}
101+
```
102+
103+
When the test runs, the request goes through Laravel's real HTTP kernel, without a web server. The response comes back as <class>\Laratesto\Testing\LaravelResponse</class>: its assertions look just like Laravel's, but they report to Testo.
104+
105+
### Using the trait
106+
107+
If your tests already extend a base class of your own, use the <class>\Laratesto\Testing\InteractsWithLaravel</class> trait instead: it gives a class the same helpers. The suite still needs the <plugin>Laravel</plugin> plugin. For example, `tests/Feature/ContainerTest.php`:
108+
109+
```php
110+
<?php
111+
112+
declare(strict_types=1);
113+
114+
namespace Tests\Feature;
115+
116+
use Laratesto\Testing\InteractsWithLaravel;
117+
use Testo\Assert;
118+
use Testo\Test;
119+
120+
#[Test]
121+
final class ContainerTest
122+
{
123+
use InteractsWithLaravel;
124+
125+
public function usesTheTestingEnvironment(): void
126+
{
127+
Assert::same($this->app()->environment(), 'testing');
128+
}
129+
}
130+
```
131+
132+
### Available helpers
133+
134+
The base class and the trait provide the same set of protected helpers:
135+
136+
- HTTP requests (`get()`, `postJson()` and others), custom headers, bearer tokens and the option to disable middleware. Cookies from a response are sent with the next request in the same test, just like in a browser.
137+
- Authentication (`actingAs()`, `assertAuthenticated()`), the session, and database assertions (`assertDatabaseHas()`, `assertModelExists()` and others).
138+
- The service container, facades and their fakes (`Queue::fake()`, `Event::fake()`).
139+
- Running Artisan commands and checking their exit code and output.
140+
141+
Laravel's fakes check their expectations through PHPUnit assertions. Laratesto ships a small stand-in for those classes, so the fakes work without PHPUnit installed. It isn't a full PHPUnit replacement, though: there's no `createMock()`, for example. For mocks, use <plugin>Mockery</plugin>. The full list of helpers and their limitations is in the [Laratesto README](https://github.com/ichinya/laratesto#http-helpers).
142+
143+
## Application lifecycle
144+
145+
Every test gets a new application instance. Setup that needs the application (the container, configuration, facades) goes into the <func>\Laratesto\Testing\InteractsWithLaravel::setUpLaravel()</func> hook, and cleanup that still needs it goes into <func>\Laratesto\Testing\InteractsWithLaravel::tearDownLaravel()</func>. Here's the order of a single test:
146+
147+
1. Laratesto boots the application and passes it to the test.
148+
2. If the test has a database attribute, the database is prepared.
149+
3. `setUpLaravel()` runs.
150+
4. The test itself runs.
151+
5. `tearDownLaravel()` runs.
152+
6. The database transaction or migrations are rolled back, and Laratesto resets Laravel's state.
153+
154+
<signature compact name="\Laratesto\Testing\InteractsWithLaravel::setUpLaravel(): void">
155+
<short>Protected hook called before each test, once the application is ready and the database is prepared.</short>
156+
</signature>
157+
158+
<signature compact name="\Laratesto\Testing\InteractsWithLaravel::tearDownLaravel(): void">
159+
<short>Protected hook called after each test, while the application is still available.</short>
160+
</signature>
161+
162+
Keep in mind that Testo creates one instance of a test class and reuses it for all its tests (see <plugin>Lifecycle</plugin>). The constructor runs only once, so reset per-test properties in `setUpLaravel()`.
163+
164+
::: warning One test at a time
165+
Laravel keeps a lot of global state, so application tests must run strictly one after another. If you use <attr>\Testo\Fiber\RunInFiber</attr>, keep the default <enum>\Testo\Fiber\Schedule::Solo</enum>: with <enum>\Testo\Fiber\Schedule::RoundRobin</enum> or <enum>\Testo\Fiber\Schedule::Random</enum> on a class, tests would interleave, so Laratesto stops them with an error.
166+
:::
167+
168+
## Database isolation
169+
170+
Laravel's `RefreshDatabase` and similar traits are built for PHPUnit. With Laratesto, use attributes with the same names from the `Laratesto\Attribute` namespace instead. You can put any of them on a test method or on the whole class.
171+
172+
For a standard Laravel application with the default `User` model and its migration, a database test looks like this (`tests/Feature/DatabaseTest.php`):
173+
174+
```php
175+
<?php
176+
177+
declare(strict_types=1);
178+
179+
namespace Tests\Feature;
180+
181+
use App\Models\User;
182+
use Laratesto\Attribute\RefreshDatabase;
183+
use Laratesto\Testing\LaravelTestCase;
184+
use Testo\Test;
185+
186+
#[Test]
187+
final class DatabaseTest extends LaravelTestCase
188+
{
189+
#[RefreshDatabase]
190+
public function persistsAUser(): void
191+
{
192+
$user = User::factory()->create();
193+
194+
$this->assertDatabaseHas('users', ['id' => $user->getKey()]);
195+
}
196+
}
197+
```
198+
199+
<signature h="3" name="#[\Laratesto\Attribute\RefreshDatabase(bool $seed = false, ?string $seeder = null, bool $dropViews = false, bool $dropTypes = false, ?array $connections = null)]">
200+
<short>Migrates the database once, then wraps each test in a transaction that is rolled back.</short>
201+
<description>
202+
203+
The fastest option for most applications. `migrate:fresh` runs once per process (and again if the schema disappears), then every test runs inside a transaction that is rolled back afterward.
204+
205+
</description>
206+
</signature>
207+
208+
<signature h="3" name="#[\Laratesto\Attribute\DatabaseTransactions(?array $connections = null)]">
209+
<short>Wraps each test in a transaction that is rolled back, without running migrations.</short>
210+
<description>
211+
212+
Use it when the schema is already in place, for example when it is created outside the tests.
213+
214+
</description>
215+
</signature>
216+
217+
<signature h="3" name="#[\Laratesto\Attribute\DatabaseMigrations(bool $seed = false, ?string $seeder = null, bool $dropViews = false, bool $dropTypes = false)]">
218+
<short>Runs `migrate:fresh` before each test and rolls the migrations back after it.</short>
219+
<description>
220+
221+
The slowest option, but every test starts with a freshly built schema.
222+
223+
</description>
224+
</signature>
225+
226+
<signature h="3" name="#[\Laratesto\Attribute\DatabaseTruncation(bool $seed = false, ?string $seeder = null, bool $dropViews = false, bool $dropTypes = false, ?array $connections = null, ?array $tables = null, ?array $exceptTables = null)]">
227+
<short>Truncates tables before each test.</short>
228+
<description>
229+
230+
Useful when the code under test can't run inside a transaction. The first time, the database is migrated with `migrate:fresh`; after that, the tables are truncated before each test and, if requested, seeded again. Nothing is cleaned up after the test.
231+
232+
</description>
233+
</signature>
234+
235+
::: tip Seeding inside a test
236+
`$this->seed()` from Laravel's `TestCase` isn't available. Use the attribute's `seed` / `seeder` parameters, or call `Artisan::call('db:seed', ['--force' => true])` in the test.
237+
:::
238+
239+
## Migrating from PHPUnit
240+
241+
Laratesto has a companion package, [`ichinya/laratesto-rector`](https://github.com/ichinya/laratesto-rector), that converts existing Laravel PHPUnit tests to Testo. It is built on Rector and reuses the generic rules of the [Rector integration](./rector.md), adding Laravel-specific ones on top: for example, it turns the `RefreshDatabase` trait into the attribute of the same name and puts <attr>\Testo\Test</attr> on test methods.
242+
243+
```bash
244+
composer require --dev ichinya/laratesto-rector
245+
```
246+
247+
Commit your tests before migrating: the migrator refuses to apply changes to files with uncommitted edits. Then run it twice, first to preview and then to apply:
248+
249+
```bash
250+
php artisan laratesto:migrate-rector # preview: files stay untouched
251+
php artisan laratesto:migrate-rector --apply # rewrite the tests
252+
git diff -- tests
253+
```
254+
255+
By default, the command processes the `tests` directory; to narrow it down, pass one or more `--path` options. Some constructs can't be converted safely, for example a database trait with non-trivial configuration. The migrator leaves such spots as they are, marks them with a `laratesto-residual` comment and lists them in `laratesto-residuals.json`. Fix them by hand, review the diff and run the tests. An empty report doesn't guarantee the tests behave exactly as before, so running them is the real check.
256+
257+
The command's exit code tells you how it went: `2` means some spots need manual work, and `1` means the migration failed.
258+
259+
The full list of options and supported constructs is in the migrator's [migration guide](https://github.com/ichinya/laratesto-rector/blob/main/MIGRATING.md).
260+
261+
## Skills for AI agents
262+
263+
Laratesto ships skills for AI agents. They are installed the same way as Testo's, through [`llm/skills`](../intro/ai-agents.md#skills-for-agents).

0 commit comments

Comments
 (0)