@@ -70,7 +70,7 @@ function (EmailMessageInterface $message): void {
7070 }
7171
7272 /**
73- * Ensures shipment emails created in async mode are dispatched only after the cron runs .
73+ * Tests shipment email async behavior: not sent immediately, then sent by cron .
7474 *
7575 * @return void
7676 * @throws LocalizedException
@@ -92,33 +92,87 @@ function (EmailMessageInterface $message): void {
9292 DataFixture(SetPaymentMethodFixture::class, ['cart_id ' => '$cart.id$ ' , 'method ' => 'checkmo ' ]),
9393 DataFixture(PlaceOrderFixture::class, ['cart_id ' => '$cart.id$ ' ], 'order ' ),
9494 ]
95- public function testShipmentEmailDispatchedByCron (): void
95+ public function testShipmentAsyncEmailBehavior (): void
9696 {
9797 Bootstrap::getInstance ()->loadArea (Area::AREA_ADMINHTML );
98+ $ shipment = $ this ->createShipmentForOrder ();
99+ $ objectManager = Bootstrap::getObjectManager ();
100+ $ shipmentNotifier = $ objectManager ->get (ShipmentNotifier::class);
101+ $ notifyResult = $ shipmentNotifier ->notify ($ shipment );
102+ $ this ->assertFalse (
103+ $ notifyResult ,
104+ 'ShipmentNotifier::notify should defer sending when async mode is active. '
105+ );
106+ $ this ->assertCount (
107+ 0 ,
108+ $ this ->sentEmails ,
109+ 'Email must not be sent immediately in async mode. '
110+ );
111+ $ cron = $ objectManager ->get ('SalesShipmentSendEmailsCron ' );
112+ $ cron ->execute ();
113+ $ this ->assertCount (
114+ 1 ,
115+ $ this ->sentEmails ,
116+ 'One shipment email should be dispatched after cron execution. '
117+ );
118+ $ this ->assertShipmentEmailContent ($ this ->sentEmails [0 ]);
119+ }
98120
99- $ fixtures = DataFixtureStorageManager::getStorage ();
100- /** @var Order $fixtureOrder */
101- $ fixtureOrder = $ fixtures ->get ('order ' );
102-
121+ /**
122+ * Creates an invoice for the given order.
123+ *
124+ * @param Order $order
125+ * @return void
126+ * @throws LocalizedException
127+ */
128+ private function createInvoiceForOrder (Order $ order ): void
129+ {
103130 $ objectManager = Bootstrap::getObjectManager ();
104- $ orderRepository = $ objectManager ->get (OrderRepositoryInterface::class);
105131 $ invoiceService = $ objectManager ->get (InvoiceService::class);
106132 $ invoiceRepository = $ objectManager ->get (InvoiceRepositoryInterface::class);
107- $ shipmentRepository = $ objectManager ->get (ShipmentRepositoryInterface::class);
108- $ shipmentNotifier = $ objectManager ->get (ShipmentNotifier::class);
109- $ shipmentFactory = $ objectManager ->get (ShipmentFactory::class);
110-
111- $ order = $ orderRepository ->get ((int )$ fixtureOrder ->getEntityId ());
112- $ this ->assertTrue ($ order ->canInvoice (), 'Order must be invoiceable before creating a shipment. ' );
113-
133+ $ orderRepository = $ objectManager ->get (OrderRepositoryInterface::class);
114134 $ invoice = $ invoiceService ->prepareInvoice ($ order );
115135 $ invoice ->register ();
116136 $ invoice ->setSendEmail (false );
117137 $ invoiceRepository ->save ($ invoice );
118138 $ orderRepository ->save ($ order );
139+ }
140+
141+ /**
142+ * Creates a shipment for the order from fixtures.
143+ *
144+ * @return \Magento\Sales\Api\Data\ShipmentInterface
145+ * @throws LocalizedException
146+ */
147+ private function createShipmentForOrder ()
148+ {
149+ $ fixtures = DataFixtureStorageManager::getStorage ();
150+ /** @var Order $fixtureOrder */
151+ $ fixtureOrder = $ fixtures ->get ('order ' );
152+ $ objectManager = Bootstrap::getObjectManager ();
153+ $ orderRepository = $ objectManager ->get (OrderRepositoryInterface::class);
154+ $ shipmentRepository = $ objectManager ->get (ShipmentRepositoryInterface::class);
155+ $ shipmentFactory = $ objectManager ->get (ShipmentFactory::class);
156+ $ order = $ orderRepository ->get ((int )$ fixtureOrder ->getEntityId ());
157+ $ this ->createInvoiceForOrder ($ order );
158+ $ quantities = $ this ->calculateShippableQuantities ($ order );
159+ $ shipment = $ shipmentFactory ->create ($ order , $ quantities );
160+ $ shipment ->register ();
161+ $ shipment ->setSendEmail (true );
162+ $ shipmentRepository ->save ($ shipment );
163+ $ orderRepository ->save ($ shipment ->getOrder ());
119164
120- $ this ->assertTrue ($ order ->canShip (), 'Order must be shippable after invoicing. ' );
165+ return $ shipment ;
166+ }
121167
168+ /**
169+ * Calculates shippable quantities for order items.
170+ *
171+ * @param Order $order
172+ * @return array
173+ */
174+ private function calculateShippableQuantities (Order $ order ): array
175+ {
122176 $ quantities = [];
123177 foreach ($ order ->getAllItems () as $ orderItem ) {
124178 if ($ orderItem ->getIsVirtual ()) {
@@ -129,39 +183,28 @@ public function testShipmentEmailDispatchedByCron(): void
129183 $ quantities [$ orderItem ->getItemId ()] = $ qtyToShip ;
130184 }
131185 }
132- $ this ->assertNotEmpty ($ quantities , 'At least one shippable item is required. ' );
133-
134- $ shipment = $ shipmentFactory ->create ($ order , $ quantities );
135- $ shipment ->register ();
136- $ shipment ->setSendEmail (true );
137-
138- $ shipmentRepository ->save ($ shipment );
139- $ orderRepository ->save ($ shipment ->getOrder ());
186+ return $ quantities ;
187+ }
140188
141- $ notifyResult = $ shipmentNotifier ->notify ($ shipment );
142- $ this ->assertFalse (
143- $ notifyResult ,
144- 'ShipmentNotifier::notify should return false while the email is queued for async sending. '
145- );
146- $ this ->assertCount (
147- 0 ,
148- $ this ->sentEmails ,
149- 'No shipment email should be dispatched immediately. '
189+ /**
190+ * Asserts shipment email has correct content.
191+ *
192+ * @param EmailMessageInterface $email
193+ * @return void
194+ */
195+ private function assertShipmentEmailContent (EmailMessageInterface $ email ): void
196+ {
197+ $ this ->assertInstanceOf (EmailMessageInterface::class, $ email );
198+ $ this ->assertStringContainsString (
199+ 'order has shipped ' ,
200+ $ email ->getSubject (),
201+ 'Email subject should contain shipment confirmation text. '
150202 );
151-
152- $ cron = $ objectManager ->get ('SalesShipmentSendEmailsCron ' );
153- $ cron ->execute ();
154- $ cron ->execute ();
155-
156- $ this ->assertCount (
157- 1 ,
158- $ this ->sentEmails ,
159- 'One shipment email should be dispatched after cron execution. '
203+ $ this ->assertEquals (
204+ 'async-shipment@example.com ' ,
205+ $ email ->getTo ()[0 ]->getEmail (),
206+ 'Email should be sent to the customer email address. '
160207 );
161- $ email = $ this ->sentEmails [0 ];
162- $ this ->assertInstanceOf (EmailMessageInterface::class, $ email );
163- $ this ->assertStringContainsString ('order has shipped ' , $ email ->getSubject ());
164- $ this ->assertEquals ('async-shipment@example.com ' , $ email ->getTo ()[0 ]->getEmail ());
165208 }
166209
167210 /**
0 commit comments