Testing
- Introduction
- Making Turbo & Turbo Native HTTP requests
- Testing Turbo Stream HTTP Responses
- Fluent Turbo Stream Testing
- Testing Turbo Stream Broadcasts
Introduction
There are two aspects of your application using Turbo Laravel that are specific this approach itself:
- Turbo Stream HTTP responses. As you return Turbo Stream responses from your route handlers/controllers to be applied by Turbo itself; and
-
Turbo Stream broadcasts. Which is the side-effect of certain model changes, or when you call
$model->broadcastAppend()
on your models, or when you're using Handmade Turbo Stream broadcasts.
We're going to cover both of these scenarios here.
Making Turbo & Turbo Native HTTP requests
To enhance your testing capabilities here, Turbo Laravel adds a couple of macros to the TestResponse that Laravel uses under the hood. The goal is that testing Turbo Stream responses is as convenient as testing regular HTTP responses.
To mimic Turbo requests, which means sending a request setting the correct Content-Type in the Accept:
HTTP header, you need to use the InteractsWithTurbo
trait to your testcase. Now you can mimic a Turbo HTTP request by using the $this->turbo()
method before you make the HTTP call itself. You can also mimic Turbo Native specific requests by using the $this->turboNative()
also before you make the HTTP call. The first method will add the correct Turbo Stream content type to the Accept:
header, and the second method will add Turbo Native User-Agent:
value.
These methods are handy when you are conditionally returning Turbo Stream responses based on the request()->wantsTurboStream()
helper, for instance. Or when using the @turbonative
or @unlessturbonative
Blade directives.
Testing Turbo Stream HTTP Responses
You can test if you got a Turbo Stream response by using the assertTurboStream
. Similarly, you can assert that your response is not a Turbo Stream response by using the assertNotTurboStream()
macro:
use Tonysm\TurboLaravel\Testing\InteractsWithTurbo; class CreateTodosTest extends TestCase{ use InteractsWithTurbo; /** @test */ public function creating_todo_from_turbo_request_returns_turbo_stream_response() { $response = $this->turbo()->post(route('todos.store'), [ 'content' => 'Test the app', ]); $response->assertTurboStream(); } /** @test */ public function creating_todo_from_regular_request_does_not_return_turbo_stream_response() { // Notice we're not chaining the `$this->turbo()` method here. $response = $this->post(route('todos.store'), [ 'content' => 'Test the app', ]); $response->assertNotTurboStream(); }}
The controller for such response would be something like this:
class TodosController{ public function store() { $todo = auth()->user()->todos()->create(request()->validate([ 'content' => ['required'], ])); if (request()->wantsTurboStream()) { return response()->turboStream($todo); } return redirect()->route('todos.index'); }}
Fluent Turbo Stream Testing
You can get specific on your Turbo Stream responses by passing a callback to the assertTurboStream(fn)
method. This can be used to test that you have a specific Turbo Stream tag being returned, or that you're returning exactly 2 Turbo Stream tags, for instance:
/** @test */public function create_todos(){ $this->get(route('todos.store')) ->assertTurboStream(fn (AssertableTurboStream $turboStreams) => ( $turboStreams->has(2) && $turboStreams->hasTurboStream(fn ($turboStream) => ( $turboStream->where('target', 'flash_messages') ->where('action', 'prepend') ->see('Todo was successfully created!') )) && $turboStreams->hasTurboStream(fn ($turboStream) => ( $turboStream->where('target', 'todos') ->where('action', 'append') ->see('Test the app') )) ));}
Testing Turbo Stream Broadcasts
All broadcasts use the TurboStream
Facade. You may want to fake it so you can that the broadcasts are being correctly sent:
use App\Models\Todo;use Tonysm\TurboLaravel\Facades\TurboStream;use Tonysm\TurboLaravel\Broadcasting\PendingBroadcast; class CreatesCommentsTest extends TestCase{ /** @test */ public function content_is_required() { TurboStream::fake(); $todo = Todo::factory()->create(); $this->turbo()->post(route('todos.comments.store', $todo), [ 'content' => null, ])->assertInvalid(['content']); TurboStream::assertNothingWasBroadcasted(); } /** @test */ public function creates_comments() { TurboStream::fake(); $todo = Todo::factory()->create(); $this->turbo()->post(route('todos.comments.store', $todo), [ 'content' => 'Hey, this is really nice!', ])->assertTurboStream(); TurboStream::assertBroadcasted(function (PendingBroadcast $broadcast) use ($todo) { return $broadcast->target === 'comments' && $broadcast->action === 'append' && $broadcast->partialView === 'comments._comment' && $broadcast->partialData['comment']->is($todo->comments->first()) && count($broadcast->channels) === 1 && $broadcast->channels[0]->name === sprintf('private-%s', $todo->broadcastChannel()); }); }}
Note: If you're using the automatic model changes broadcasting, make sure your turbo-laravel.queue
config key is set to false, otherwise actions may not be dispatched during test because the model observer only fires them after the transaction is commited, which never happens in tests since they run inside a transaction.