Scroll down to the script below, click on any sentence (including terminal blocks!) to jump to that spot in the video!
If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeA few minutes ago, in the dev
environment only, we overrode all our transports so that all messages were handled synchronously. We commented it out for now, but this is also something that you could choose to do in your test
environment, so that when you run the tests, the messages are handled within the test.
This may or may not be what you want. On one hand, it means your functional test is testing more. On the other hand, a functional test should probably test that the endpoint works and the message is sent to the transport, but testing the handler itself should be done in a test specifically for that class.
That's what we're going to do now: figure out a way to not run the handlers synchronously but test that the message was sent to the transport. Sure, if we killed the worker, we could query the messenger_messages
table, but that's a bit hacky - and only works if you're using the Doctrine transport. Fortunately, there's a more interesting option.
Start by copying config/packages/dev/messenger.yaml
and pasting that into config/packages/test/
. This gives us messenger configuration that will only be used in the test
environment. Uncomment the code, and replace sync
with in-memory
. Do that for both of the transports.
framework: | |
messenger: | |
transports: | |
async: 'in-memory://' | |
async_priority_high: 'in-memory://' |
The in-memory
transport is really cool. In fact, let's look at it! I'll hit Shift+Shift
in PhpStorm and search for InMemoryTransport
to find it.
This... is basically a fake transport. When a message is sent to it, it doesn't handle it or send it anywhere, it stores it in a property. If you were to use this in a real project, the messages would then disappear at the end of the request.
But, this is super useful for testing. Let's try it. A second ago, each time we ran our test, our worker actually started processing those messages... which makes sense: we really were delivering them to the transport. Now, I'll clear the screen and then run:
php bin/phpunit
It still works... but now the worker does nothing: the message isn't really being sent to the transport anymore and it's lost at the end of our tests. But! From within the test, we can now fetch that transport and ask it how many messages were sent to it!
Behind the scenes, every transport is actually a service in the container. Find your open terminal and run:
php bin/console debug:container async
There they are: messenger.transport.async
and messenger.transport.async_priority_high
. Copy the second service id.
We want to verify that the AddPonkaToImage
message is sent to the transport, and we know that it's being routed to async_priority_high
.
Back in the test, this is super cool: we can fetch the exact transport object that was just used from within the test by saying: $transport =
self::$container->get() and then pasting the service id: messenger.transport.async_priority_high
... lines 1 - 8 | |
class ImagePostControllerTest extends WebTestCase | |
{ | |
public function testCreate() | |
{ | |
... lines 13 - 25 | |
$transport = self::$container->get('messenger.transport.async_priority_high'); | |
... line 27 | |
} | |
} |
This self::$container
property holds the container that was actually used during the test request and is designed so that we can fetch anything we want out of it.
Let's see what this looks like: dd($transport)
.
... lines 1 - 8 | |
class ImagePostControllerTest extends WebTestCase | |
{ | |
public function testCreate() | |
{ | |
... lines 13 - 25 | |
$transport = self::$container->get('messenger.transport.async_priority_high'); | |
dd($transport); | |
} | |
} |
Now jump back over to your terminal and run:
php bin/phpunit
Nice! This dumps the InMemoryTransport
object and... the sent
property indeed holds our one message object! All we need to do now is add an assertion for this.
Back in the test, I'm going to help out my editor by adding some inline docs to advertise that this is an InMemoryTransport
. Below add $this->assertCount()
to assert that we expect one message to be returned when we say $transport->
... let's see... the method that you can call on a transport to get the sent, or "queued" messages is get()
.
... lines 1 - 6 | |
use Symfony\Component\Messenger\Transport\InMemoryTransport; | |
class ImagePostControllerTest extends WebTestCase | |
{ | |
public function testCreate() | |
{ | |
... lines 13 - 24 | |
/** @var InMemoryTransport $transport */ | |
$transport = self::$container->get('messenger.transport.async_priority_high'); | |
$this->assertCount(1, $transport->get()); | |
} | |
} |
Let's try it! Run:
php bin/phpunit
Got it! We're now guaranteeing that the message was sent but we've kept our tests faster and more directed by not trying to handle them synchronously. If we were using something like RabbitMQ, we also don't need to have that running whenever we execute our tests.
Next, let's talk deployment! How do we run our workers on production... and make sure they stay running?