Slide 1

Slide 1 text

WITH BEHAT, UI AND API BUSINESS LOGIC TESTING @mpzalewski Zales0123 MATEUSZ ZALEWSKI

Slide 2

Slide 2 text

BEHAVIOUR DRIVEN DEVELOPMENT BDD is a process designed to aid the management and the delivery of software development projects by improving communication between engineers and business professionals. https://inviqa.com/insights/bdd-guide 2

Slide 3

Slide 3 text

3

Slide 4

Slide 4 text

4

Slide 5

Slide 5 text

5

Slide 6

Slide 6 text

GOALS 6

Slide 7

Slide 7 text

Substitutability Reusability Fun 7

Slide 8

Slide 8 text

Background: Given the store operates on a single channel in "United States" And the store has a product "T-shirt banana" priced at "$12.54" And the store ships everywhere for free @ui Scenario: Adding a simple product to the cart When I add this product to the cart Then I should be on my cart summary page And I should be notified that the product has been successfully added And there should be one item in my cart And this item should have name "T-shirt banana" 8

Slide 9

Slide 9 text

Background: Given the store operates on a single channel in "United States" And the store has a product "T-shirt banana" priced at "$12.54" And the store ships everywhere for free @ui Scenario: Adding a simple product to the cart When I add this product to the cart Then I should be on my cart summary page And I should be notified that the product has been successfully added And there should be one item in my cart And this item should have name "T-shirt banana" 9

Slide 10

Slide 10 text

Background: Given the store operates on a single channel in "United States" And the store has a product "T-shirt banana" priced at "$12.54" And the store ships everywhere for free @ui Scenario: Adding a simple product to the cart When I add this product to the cart Then I should be on my cart summary page And I should be notified that the product has been successfully added And there should be one item in my cart And this item should have name "T-shirt banana" @ui 10

Slide 11

Slide 11 text

Given the store has a product "T-shirt banana" priced at "$12.54" /** * @Given /^the store has a product "([^"]+)" priced at ("[^"]+")$/ */ public function storeHasAProductPricedAt( string $productName, int $price = 100 ): void { $product = $this->createProduct($productName, $price, $channel); $this->saveProduct($product); } 11

Slide 12

Slide 12 text

Given the store has a product "T-shirt banana" priced at "$12.54" /** * @Given /^the store has a product "([^"]+)" priced at ("[^"]+")$/ */ public function storeHasAProductPricedAt( string $productName, int $price = 100 ): void { $product = $this->createProduct($productName, $price, $channel); $this->saveProduct($product); } $product = $this->createProduct($productName, $price, $channel); 12

Slide 13

Slide 13 text

private function createProduct( string $productName, int $price = 100, ChannelInterface $channel = null ): ProductInterface { // ... $product = $this->productFactory->createWithVariant(); $product->setCode(StringInflector::nameToUppercaseCode($productName)); $product->setName($productName); // ... $productVariant = $this->defaultVariantResolver->getVariant($product); $productVariant->addChannelPricing( $this->createChannelPricingForChannel($price, $channel) ); // ... return $product; } 13

Slide 14

Slide 14 text

Given the store has a product "T-shirt banana" priced at "$12.54" /** * @Given /^the store has a product "([^"]+)" priced at ("[^"]+")$/ */ public function storeHasAProductPricedAt( string $productName, int $price = 100 ): void { $product = $this->createProduct($productName, $price, $channel); $this->saveProduct($product); } priced at ("[^"]+") int $price = 100 14

Slide 15

Slide 15 text

/** * @Transform /^"(\-)?(?:€|£|¥|\$)((?:\d+\.)?\d+)"$/ */ public function getPriceFromString(string $sign, string $price): int { $this->validatePriceString($price); $price = (int) round((float) $price * 100, 2); if ('-' === $sign) { $price *= -1; } return $price; } 15

Slide 16

Slide 16 text

When I add this product to the cart /** * @When /^I (?:add|added) (this product) to the cart$/ */ public function iAddProductToTheCart(ProductInterface $product): void { $this->productShowPage->open(['slug' => $product->getSlug()]); $this->productShowPage->addToCart(); } @ui 16

Slide 17

Slide 17 text

When I add this product to the cart /** * @When /^I (?:add|added) (this product) to the cart$/ */ public function iAddProductToTheCart(ProductInterface $product): void { $this->productShowPage->open(['slug' => $product->getSlug()]); $this->productShowPage->addToCart(); } (this product) ProductInterface $product 17 @ui

Slide 18

Slide 18 text

/** * @Transform /^(?:this|that|the) ([^"]+)$/ */ public function getResource(mixed $resource): mixed { return $this->sharedStorage->get( StringInflector::nameToCode($resource) ); } 18

Slide 19

Slide 19 text

private function saveProduct(ProductInterface $product) { $this->productRepository->add($product); $this->sharedStorage->set('product', $product); } public function set($key, $resource): void { $this->clipboard[$key] = $resource; $this->latestKey = $key; } 19

Slide 20

Slide 20 text

private function saveProduct(ProductInterface $product) { $this->productRepository->add($product); $this->sharedStorage->set('product', $product); } /** * @Transform /^(?:this|that|the) ([^"]+)$/ */ public function getResource(string $resource): mixed { return $this->sharedStorage->get( StringInflector::nameToCode($resource) ); } /** * @Given /^I (?:add|added) (this product) to the cart$/ */ public function iAddProductToTheCart(ProductInterface $product): void { $this->productShowPage->open(['slug' => $product->getSlug()]); $this->productShowPage->addToCart(); } 20

Slide 21

Slide 21 text

$this->productShowPage->open(['slug' => $product->getSlug()]); public function open(array $urlParameters = []): void { $this->tryToOpen($urlParameters); $this->verify($urlParameters); } public function tryToOpen(array $urlParameters = []): void { $this->getSession()->visit($this->getUrl($urlParameters)); } public function verify(array $urlParameters = []): void { $this->verifyStatusCode(); $this->verifyUrl($urlParameters); } 21 @ui

Slide 22

Slide 22 text

$this->productShowPage->addToCart(); public function addToCart(): void { $this->getElement('add_to_cart_button')->click(); } protected function getDefinedElements(): array { return array_merge(parent::getDefinedElements(), [ 'add_to_cart_button' => '#addToCart button', // ... ]); } 22 @ui

Slide 23

Slide 23 text

Then there should be one item in my cart /** * @Then there should be one item in my cart */ public function thereShouldBeOneItemInMyCart() { Assert::true($this->summaryPage->isSingleItemOnPage()); } @ui 23

Slide 24

Slide 24 text

$this->summaryPage->isSingleItemOnPage(); public function isSingleItemOnPage(): bool { $items = $this ->getElement('cart_items') ->findAll('css', '[data-test-cart-product-row]') ; return 1 === count($items); } 24 @ui

Slide 25

Slide 25 text

SCENARIO CONTEXT PAGE UI Substitutability 25

Slide 26

Slide 26 text

26

Slide 27

Slide 27 text

Background: Given the store operates on a single channel in "United States" And the store has a product "T-shirt banana" priced at "$12.54" And the store ships everywhere for free @ui @api Scenario: Adding a simple product to the cart When I add this product to the cart Then I should be on my cart summary page And I should be notified that the product has been successfully added And there should be one item in my cart And this item should have name "T-shirt banana" 27

Slide 28

Slide 28 text

Background: Given the store operates on a single channel in "United States" And the store has a product "T-shirt banana" priced at "$12.54" And the store ships everywhere for free @ui @api Scenario: Adding a simple product to the cart When I add this product to the cart Then I should be on my cart summary page And I should be notified that the product has been successfully added And there should be one item in my cart And this item should have name "T-shirt banana" @api 28

Slide 29

Slide 29 text

Given the store has a product "T-shirt banana" priced at "$12.54" /** * @Given /^the store has a product "([^"]+)" priced at ("[^"]+")$/ */ public function storeHasAProductPricedAt( string $productName, int $price = 100 ): void { $product = $this->createProduct($productName, $price, $channel); $this->saveProduct($product); } 29

Slide 30

Slide 30 text

When I add this product to the cart /** * @When /^I (?:add|added) (this product) to the (cart)$/ */ public function iAddThisProductToTheCart( ProductInterface $product, ?string $tokenValue ): void { $tokenValue = $this->pickupCart(); $request = Request::customItemAction( 'shop', 'orders', $tokenValue, HttpRequest::METHOD_POST, 'items' ); $request->updateContent([ 'productVariant' => // variant identifier, 'quantity' => 1, ]); $this->cartClient->executeCustomRequest($request); $this->sharedStorage->set('product', $product); } 30 @api

Slide 31

Slide 31 text

When I add this product to the cart /** * @When /^I (?:add|added) (this product) to the (cart)$/ */ public function iAddThisProductToTheCart( ProductInterface $product, ?string $tokenValue ): void { $tokenValue = $this->pickupCart(); $request = Request::customItemAction( 'shop', 'orders', $tokenValue, HttpRequest::METHOD_POST, 'items' ); $request->updateContent([ 'productVariant' => // variant identifier, 'quantity' => 1, ]); $this->cartClient->executeCustomRequest($request); $this->sharedStorage->set('product', $product); } $request = Request::customItemAction( 'shop', 'orders', $tokenValue, HttpRequest::METHOD_POST, 'items' ); @api 31

Slide 32

Slide 32 text

Request::customItemAction public static function customItemAction( ?string $section, string $resource, string $id, string $type, string $action ): RequestInterface { return new self( sprintf('/api/v2/%s/%s/%s/%s', $section, $resource, $id, $action), $type, ['CONTENT_TYPE' => self::resolveHttpMethod($type)], ); } 32 @api

Slide 33

Slide 33 text

POST /shop/orders/{tokenValue}/items input Sylius\Bundle\ApiBundle\Command\Cart\AddItemToCart shop:cart:read shop:cart:add_item Adds Item to cart @api 33

Slide 34

Slide 34 text

POST /api/v2/shop/orders/HcgfktTi8E/items HTTP/2 host: demo.sylius.com accept: application/json content-type: application/json content-length: 90 { "productVariant": "/api/v2/shop/product-variants/variant1", "quantity": 3 } 34

Slide 35

Slide 35 text

Then there should be one item in my cart /** * @Then there should be one item in my cart */ public function thereShouldBeOneItemInMyCart(): void { $response = $this->cartsClient->getLastResponse(); $items = $this->responseChecker->getValue($response, 'items'); Assert::count($items, 1); } 35 @api

Slide 36

Slide 36 text

public function getValue(Response $response, string $key): mixed { $content = json_decode($response->getContent(), true); Assert::isArray($content); Assert::keyExists($content, $key); return $content[$key]; } $items = $this->responseChecker->getValue($response, 'items'); @api 36

Slide 37

Slide 37 text

{ "@context": "/api/v2/contexts/Order", "@id": "/api/v2/shop/orders/{orderToken}", "@type": „Order", // ... "items": [ { "@id": "/api/v2/shop/order-items/866997", "@type": "OrderItem", "variant": "/api/v2/shop/product-variants/variant1", // ... } ], // ... } 37

Slide 38

Slide 38 text

SCENARIO UI CONTEXT PAGE UI API CONTEXT CLIENT Reusability 38

Slide 39

Slide 39 text

Background: Given the store operates on a single channel in "United States" And the store has a product "T-shirt banana" priced at "$12.54" And the store ships everywhere for free @ui @api @application Scenario: Adding a simple product to the cart When I add this product to the cart Then I should be on my cart summary page And I should be notified that the product has been successfully added And there should be one item in my cart And this item should have name "T-shirt banana" 39

Slide 40

Slide 40 text

Background: Given the store operates on a single channel in "United States" And the store has a product "T-shirt banana" priced at "$12.54" And the store ships everywhere for free @ui @api @application Scenario: Adding a simple product to the cart When I add this product to the cart Then I should be on my cart summary page And I should be notified that the product has been successfully added And there should be one item in my cart And this item should have name "T-shirt banana" @application 40

Slide 41

Slide 41 text

Given the store has a product "T-shirt banana" priced at "$12.54" /** * @Given /^the store has a product "([^"]+)" priced at ("[^"]+")$/ */ public function storeHasAProductPricedAt( string $productName, int $price = 100 ): void { $product = $this->createProduct($productName, $price, $channel); $this->saveProduct($product); } 41

Slide 42

Slide 42 text

When I add this product to the cart @application /** * @When /^I add (this product) to the cart$/ */ public function iAddProductToTheCart(ProductInterface $product): void { $cart = $this->cartContext->getCart(); try { $this ->commandBus ->dispatch(new AddToCart($cart, $product, 1)) ; } catch (HandlerFailedException $exception) { $this ->sharedStorage ->set('last_exception', $exception->getPrevious()) ; } } 42

Slide 43

Slide 43 text

When I add this product to the cart /** * @When /^I add (this product) to the cart$/ */ public function iAddProductToTheCart(ProductInterface $product): void { $cart = $this->cartContext->getCart(); try { $this ->commandBus ->dispatch(new AddToCart($cart, $product, 1)) ; } catch (HandlerFailedException $exception) { $this ->sharedStorage ->set('last_exception', $exception->getPrevious()) ; } } $this ->sharedStorage ->set('last_exception', $exception->getPrevious()) ; And I should be notified that the product has been successfully added @application 43

Slide 44

Slide 44 text

final readonly class AddToCart { public function __construct( public OrderInterface $cart, public ProductInterface $product, public int $quantity, ) { } } 44 @application

Slide 45

Slide 45 text

final class AddToCartHandler implements MessageHandlerInterface { public function __construct( private FactoryInterface $orderItemFactory, private OrderModifierInterface $orderModifier, private OrderItemQuantityModifierInterface $orderItemQuantityModifier, private ProductVariantResolverInterface $productVariantResolver, private EntityManagerInterface $entityManager, ) { } public function __invoke(AddToCart $command): void { $variant = $this->productVariantResolver->getVariant($command->product); $orderItem = $this->orderItemFactory->createNew(); $orderItem->setVariant($variant); $this->orderItemQuantityModifier->modify($orderItem, $command->quantity); $this->orderModifier->addToOrder($command->cart, $orderItem); $this->entityManager->persist($command->cart); $this->entityManager->flush(); } } @application 45

Slide 46

Slide 46 text

Then there should be one item in my cart /** * @Then there should be one item in my cart */ public function thereShouldBeOneItemInMyCart(): void { $cart = ($this->latestCartQuery)(); Assert::count($cart->getItems(), 1); $cartItem = $cart->getItems()->first(); $this->sharedStorage->set('item', $cartItem); } 46 @application

Slide 47

Slide 47 text

final class LatestCartQuery { public function __construct(private OrderRepositoryInterface $orderRepository) { } public function __invoke(): OrderInterface { return $this->orderRepository->findLatestCart(); } } @application 47

Slide 48

Slide 48 text

imports: - suites/application/cart/shopping_cart.yaml # ... - suites/api/admin/login.yml - suites/api/cart/accessing_cart.yml - suites/api/cart/shopping_cart.yml - suites/api/channel/channels.yml - suites/api/channel/managing_channels.yml # ... - suites/ui/admin/locale.yml - suites/ui/admin/login.yml - suites/ui/cart/shopping_cart.yml - suites/ui/channel/channels.yml # ... 49

Slide 49

Slide 49 text

default: suites: application_shopping_cart: contexts: - sylius.behat.context.hook.doctrine_orm - sylius.behat.context.transform.channel - sylius.behat.context.transform.currency - sylius.behat.context.transform.lexical - sylius.behat.context.transform.shared_storage - sylius.behat.context.transform.product - sylius.behat.context.transform.product_option - sylius.behat.context.transform.product_variant - sylius.behat.context.transform.shipping_category - sylius.behat.context.transform.tax_category - sylius.behat.context.transform.zone - sylius.behat.context.setup.channel - sylius.behat.context.setup.currency - sylius.behat.context.setup.exchange_rate - sylius.behat.context.setup.product - sylius.behat.context.setup.promotion - sylius.behat.context.setup.shipping - sylius.behat.context.setup.shipping_category - sylius.behat.context.setup.shop_security - sylius.behat.context.setup.taxation - sylius.behat.context.setup.user - sylius.behat.context.setup.zone - Sylius\Behat\Context\Application\CartContext filters: tags: "@shopping_cart && @application" 50

Slide 50

Slide 50 text

default: suites: application_shopping_cart: contexts: - sylius.behat.context.hook.doctrine_orm - sylius.behat.context.transform.channel - sylius.behat.context.transform.currency - sylius.behat.context.transform.lexical - sylius.behat.context.transform.shared_storage - sylius.behat.context.transform.product - sylius.behat.context.transform.product_option - sylius.behat.context.transform.product_variant - sylius.behat.context.transform.shipping_category - sylius.behat.context.transform.tax_category - sylius.behat.context.transform.zone - sylius.behat.context.setup.channel - sylius.behat.context.setup.currency - sylius.behat.context.setup.exchange_rate - sylius.behat.context.setup.product - sylius.behat.context.setup.promotion - sylius.behat.context.setup.shipping - sylius.behat.context.setup.shipping_category - sylius.behat.context.setup.shop_security - sylius.behat.context.setup.taxation - sylius.behat.context.setup.user - sylius.behat.context.setup.zone - Sylius\Behat\Context\Application\CartContext filters: tags: "@shopping_cart && @application" - sylius.behat.context.hook.doctrine_orm - sylius.behat.context.transform.channel - sylius.behat.context.transform.currency - sylius.behat.context.transform.lexical - sylius.behat.context.transform.shared_storage - sylius.behat.context.transform.product - sylius.behat.context.transform.product_option - sylius.behat.context.transform.product_variant - sylius.behat.context.transform.shipping_category - sylius.behat.context.transform.tax_category - sylius.behat.context.transform.zone - sylius.behat.context.setup.channel - sylius.behat.context.setup.currency - sylius.behat.context.setup.exchange_rate - sylius.behat.context.setup.product - sylius.behat.context.setup.promotion - sylius.behat.context.setup.shipping - sylius.behat.context.setup.shipping_category - sylius.behat.context.setup.shop_security - sylius.behat.context.setup.taxation - sylius.behat.context.setup.user - sylius.behat.context.setup.zone - Sylius\Behat\Context\Application\CartContext 51

Slide 51

Slide 51 text

default: suites: application_shopping_cart: contexts: - sylius.behat.context.hook.doctrine_orm - sylius.behat.context.transform.channel - sylius.behat.context.transform.currency - sylius.behat.context.transform.lexical - sylius.behat.context.transform.shared_storage - sylius.behat.context.transform.product - sylius.behat.context.transform.product_option - sylius.behat.context.transform.product_variant - sylius.behat.context.transform.shipping_category - sylius.behat.context.transform.tax_category - sylius.behat.context.transform.zone - sylius.behat.context.setup.channel - sylius.behat.context.setup.currency - sylius.behat.context.setup.exchange_rate - sylius.behat.context.setup.product - sylius.behat.context.setup.promotion - sylius.behat.context.setup.shipping - sylius.behat.context.setup.shipping_category - sylius.behat.context.setup.shop_security - sylius.behat.context.setup.taxation - sylius.behat.context.setup.user - sylius.behat.context.setup.zone - Sylius\Behat\Context\Application\CartContext filters: tags: "@shopping_cart && @application" tags: "@shopping_cart && @application" 52

Slide 52

Slide 52 text

default: suites: api_v1_shopping_cart: contexts: - Sylius\Behat\Context\Api\V1\CartContext filters: tags: "@shopping_cart && @api && @v1” api_v2_shopping_cart: contexts: - Sylius\Behat\Context\Api\V2\CartContext filters: tags: "@shopping_cart && @api && @v2" 53

Slide 53

Slide 53 text

Background: Given the store operates on a single channel in "United States" And the store has a product "T-shirt banana" priced at "$12.54" And the store ships everywhere for free @ui @api @application @mixed Scenario: Adding a simple product to the cart When I add this product to the cart Then I should be on my cart summary page And I should be notified that the product has been successfully added And there should be one item in my cart And this item should have name "T-shirt banana" 54

Slide 54

Slide 54 text

When I add this product to the cart /** * @Given /^I (?:add|added) (this product) to the cart$/ */ public function iAddProductToTheCart(ProductInterface $product): void { $this->productShowPage->open(['slug' => $product->getSlug()]); $this->productShowPage->addToCart(); } @mixed 55 Then there should be one item in my cart /** * @Then there should be one item in my cart */ public function thereShouldBeOneItemInMyCart(): void { $cart = $this->cartRepository->getLatest(); Assert::count($cart->getItems(), 1); $cartItem = $cart->getItems()->first(); }

Slide 55

Slide 55 text

When I add this product to the cart /** * @Given /^I (?:add|added) (this product) to the cart$/ */ public function iAddProductToTheCart(ProductInterface $product): void { $this->productShowPage->open(['slug' => $product->getSlug()]); $this->productShowPage->addToCart(); } @mixed 56 Then there should be one item in my cart /** * @Then there should be one item in my cart */ public function thereShouldBeOneItemInMyCart(): void { $cart = $this->cartRepository->getLatest(); Assert::count($cart->getItems(), 1); $cartItem = $cart->getItems()->first(); } ?

Slide 56

Slide 56 text

? Fun 57

Slide 57

Slide 57 text

$this->shopClient->executeCustomRequest($request); $this->adminClient->index(Resources::PRODUCT_VARIANTS); $this->client->index(); exchange-rates admin ... 58

Slide 58

Slide 58 text

59

Slide 59

Slide 59 text

60

Slide 60

Slide 60 text

61

Slide 61

Slide 61 text

Substitutability Reusability Fun 62

Slide 62

Slide 62 text

63 WHY? AND WHAT FOR?

Slide 63

Slide 63 text

Background: Given the store operates on a single channel in "United States" And the store has a product "T-shirt banana" priced at "$12.54" And the store ships everywhere for free @ui Scenario: Adding a simple product to the cart When I add this product to the cart Then I should be on my cart summary page And I should be notified that the product has been successfully added And there should be one item in my cart And this item should have name "T-shirt banana" 64

Slide 64

Slide 64 text

Feature: Adding a simple product to the cart In order to select products for purchase As a Visitor I want to be able to add simple products to cart Background: Given the store operates on a single channel in "United States" And the store has a product "T-shirt banana" priced at "$12.54" And the store ships everywhere for free @ui Scenario: Adding a simple product to the cart When I add this product to the cart Then I should be on my cart summary page And I should be notified that the product has been successfully added And there should be one item in my cart And this item should have name "T-shirt banana" 65

Slide 65

Slide 65 text

66 Feature: Adding a simple product to the cart In order to start the checkout process with the desired products As a Visitor I want to be able to add simple products to cart Background: Given the store operates on a single channel in "United States" And the store has a product "T-shirt banana" priced at "$12.54" And the store ships everywhere for free @ui Scenario: Adding a simple product to the cart When I add this product to the cart Then I should be on my cart summary page And I should be notified that the product has been successfully added And there should be one item in my cart And this item should have name "T-shirt banana"

Slide 66

Slide 66 text

67 Feature: Adding a simple product to the cart In order to start the process of changing my clothing style As a Visitor I want to be able to add simple products to cart Background: Given the store operates on a single channel in "United States" And the store has a product "T-shirt banana" priced at "$12.54" And the store ships everywhere for free @ui Scenario: Adding a simple product to the cart When I add this product to the cart Then I should be on my cart summary page And I should be notified that the product has been successfully added And there should be one item in my cart And this item should have name "T-shirt banana"

Slide 67

Slide 67 text

@mpzalewski Zales0123 mpzalewski.com 68

Slide 68

Slide 68 text

THANK YOU QR CODE @CommerceWeavers @Sylius QR CODE @mpzalewski Zales0123