diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md
index d38ddc7e6d88f..981823fd07f10 100644
--- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md
+++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md
@@ -29,6 +29,7 @@ CHANGELOG
* Added support for PHP files with translations in translation commands.
* Added support for boolean container parameters within routes.
* Added the `messenger:setup-transports` command to setup messenger transports
+ * Added a `InMemoryTransport` to Messenger. Use it with a DSN starting with `in-memory://`.
4.2.0
-----
diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/messenger.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/messenger.xml
index 1720ea72e71aa..4f677d40918a8 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/messenger.xml
+++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/messenger.xml
@@ -70,6 +70,11 @@
+
+
+
+
+
diff --git a/src/Symfony/Component/Messenger/Tests/Transport/InMemoryTransportFactoryTest.php b/src/Symfony/Component/Messenger/Tests/Transport/InMemoryTransportFactoryTest.php
new file mode 100644
index 0000000000000..856c865f1ea31
--- /dev/null
+++ b/src/Symfony/Component/Messenger/Tests/Transport/InMemoryTransportFactoryTest.php
@@ -0,0 +1,74 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Tests\Transport;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Messenger\Envelope;
+use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage;
+use Symfony\Component\Messenger\Transport\InMemoryTransport;
+use Symfony\Component\Messenger\Transport\InMemoryTransportFactory;
+use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface;
+
+/**
+ * @internal
+ *
+ * @author Gary PEGEOT
+ */
+class InMemoryTransportFactoryTest extends TestCase
+{
+ /**
+ * @var InMemoryTransportFactory
+ */
+ private $factory;
+
+ protected function setUp()
+ {
+ $this->factory = new InMemoryTransportFactory();
+ }
+
+ /**
+ * @param string $dsn
+ * @param bool $expected
+ *
+ * @dataProvider provideDSN
+ */
+ public function testSupports(string $dsn, bool $expected = true)
+ {
+ $this->assertSame($expected, $this->factory->supports($dsn, []), 'InMemoryTransportFactory::supports returned unexpected result.');
+ }
+
+ public function testCreateTransport()
+ {
+ /** @var SerializerInterface $serializer */
+ $serializer = $this->createMock(SerializerInterface::class);
+
+ $this->assertInstanceOf(InMemoryTransport::class, $this->factory->createTransport('in-memory://', [], $serializer));
+ }
+
+ public function testResetCreatedTransports()
+ {
+ $transport = $this->factory->createTransport('in-memory://', [], $this->createMock(SerializerInterface::class));
+ $transport->send(Envelope::wrap(new DummyMessage('Hello.')));
+
+ $this->assertCount(1, $transport->get());
+ $this->factory->reset();
+ $this->assertCount(0, $transport->get());
+ }
+
+ public function provideDSN(): array
+ {
+ return [
+ 'Supported' => ['in-memory://foo'],
+ 'Unsupported' => ['amqp://bar', false],
+ ];
+ }
+}
diff --git a/src/Symfony/Component/Messenger/Tests/Transport/InMemoryTransportTest.php b/src/Symfony/Component/Messenger/Tests/Transport/InMemoryTransportTest.php
new file mode 100644
index 0000000000000..8b5634b3f616a
--- /dev/null
+++ b/src/Symfony/Component/Messenger/Tests/Transport/InMemoryTransportTest.php
@@ -0,0 +1,69 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Tests\Transport;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Messenger\Envelope;
+use Symfony\Component\Messenger\Transport\InMemoryTransport;
+
+/**
+ * @internal
+ *
+ * @author Gary PEGEOT
+ */
+class InMemoryTransportTest extends TestCase
+{
+ /**
+ * @var InMemoryTransport
+ */
+ private $transport;
+
+ protected function setUp()
+ {
+ $this->transport = new InMemoryTransport();
+ }
+
+ public function testSend()
+ {
+ $envelope = new Envelope(new \stdClass());
+ $this->transport->send($envelope);
+ $this->assertSame([$envelope], $this->transport->get());
+ }
+
+ public function testAck()
+ {
+ $envelope = new Envelope(new \stdClass());
+ $this->transport->ack($envelope);
+ $this->assertSame([$envelope], $this->transport->getAcknowledged());
+ }
+
+ public function testReject()
+ {
+ $envelope = new Envelope(new \stdClass());
+ $this->transport->reject($envelope);
+ $this->assertSame([$envelope], $this->transport->getRejected());
+ }
+
+ public function testReset()
+ {
+ $envelope = new Envelope(new \stdClass());
+ $this->transport->send($envelope);
+ $this->transport->ack($envelope);
+ $this->transport->reject($envelope);
+
+ $this->transport->reset();
+
+ $this->assertEmpty($this->transport->get(), 'Should be empty after reset');
+ $this->assertEmpty($this->transport->getAcknowledged(), 'Should be empty after reset');
+ $this->assertEmpty($this->transport->getRejected(), 'Should be empty after reset');
+ }
+}
diff --git a/src/Symfony/Component/Messenger/Transport/InMemoryTransport.php b/src/Symfony/Component/Messenger/Transport/InMemoryTransport.php
new file mode 100644
index 0000000000000..a21d67bd1982b
--- /dev/null
+++ b/src/Symfony/Component/Messenger/Transport/InMemoryTransport.php
@@ -0,0 +1,95 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Transport;
+
+use Symfony\Component\Messenger\Envelope;
+use Symfony\Contracts\Service\ResetInterface;
+
+/**
+ * Transport that stay in memory. Useful for testing purpose.
+ *
+ * @author Gary PEGEOT
+ *
+ * @experimental in 4.3
+ */
+class InMemoryTransport implements TransportInterface, ResetInterface
+{
+ /**
+ * @var Envelope[]
+ */
+ private $sent = [];
+
+ /**
+ * @var Envelope[]
+ */
+ private $acknowledged = [];
+
+ /**
+ * @var Envelope[]
+ */
+ private $rejected = [];
+
+ /**
+ * {@inheritdoc}
+ */
+ public function get(): iterable
+ {
+ return $this->sent;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function ack(Envelope $envelope): void
+ {
+ $this->acknowledged[] = $envelope;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function reject(Envelope $envelope): void
+ {
+ $this->rejected[] = $envelope;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function send(Envelope $envelope): Envelope
+ {
+ $this->sent[] = $envelope;
+
+ return $envelope;
+ }
+
+ public function reset()
+ {
+ $this->sent = $this->rejected = $this->acknowledged = [];
+ }
+
+ /**
+ * @return Envelope[]
+ */
+ public function getAcknowledged(): array
+ {
+ return $this->acknowledged;
+ }
+
+ /**
+ * @return Envelope[]
+ */
+ public function getRejected(): array
+ {
+ return $this->rejected;
+ }
+}
diff --git a/src/Symfony/Component/Messenger/Transport/InMemoryTransportFactory.php b/src/Symfony/Component/Messenger/Transport/InMemoryTransportFactory.php
new file mode 100644
index 0000000000000..e7088e67052ca
--- /dev/null
+++ b/src/Symfony/Component/Messenger/Transport/InMemoryTransportFactory.php
@@ -0,0 +1,45 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Messenger\Transport;
+
+use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface;
+use Symfony\Contracts\Service\ResetInterface;
+
+/**
+ * @author Gary PEGEOT
+ *
+ * @experimental in 4.3
+ */
+class InMemoryTransportFactory implements TransportFactoryInterface, ResetInterface
+{
+ /**
+ * @var InMemoryTransport[]
+ */
+ private $createdTransports = [];
+
+ public function createTransport(string $dsn, array $options, SerializerInterface $serializer): TransportInterface
+ {
+ return $this->createdTransports[] = new InMemoryTransport();
+ }
+
+ public function supports(string $dsn, array $options): bool
+ {
+ return 0 === strpos($dsn, 'in-memory://');
+ }
+
+ public function reset()
+ {
+ foreach ($this->createdTransports as $transport) {
+ $transport->reset();
+ }
+ }
+}