SlideShare a Scribd company logo
WITH BEHAT, UI AND API
BUSINESS LOGIC TESTING
@mpzalewski
Zales0123
MATEUSZ ZALEWSKI
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
3
4
5
GOALS
6
Substitutability
Reusability
Fun
7
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
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
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
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
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
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
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
/**
* @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
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
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
/**
* @Transform /^(?:this|that|the) ([^"]+)$/
*/
public function getResource(mixed $resource): mixed
{
return $this->sharedStorage->get(
StringInflector::nameToCode($resource)
);
}
18
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
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
$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
$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
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
$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
SCENARIO CONTEXT PAGE
UI
Substitutability
25
26
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
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
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
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
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
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
<itemOperation name="shop_add_item">
<attribute name="method">POST</attribute>
<attribute name="path">/shop/orders/{tokenValue}/items</attribute>
<attribute name="messenger">input</attribute>
<attribute name=„input">SyliusBundleApiBundleCommandCartAddItemToCart</attribute>
<attribute name="normalization_context">
<attribute name="groups">shop:cart:read</attribute>
</attribute>
<attribute name="denormalization_context">
<attribute name="groups">shop:cart:add_item</attribute>
</attribute>
<attribute name="openapi_context">
<attribute name="summary">Adds Item to cart</attribute>
</attribute>
</itemOperation>
@api
33
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
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
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
{
"@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
SCENARIO
UI CONTEXT PAGE
UI
API CONTEXT CLIENT
Reusability
38
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
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
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
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
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
final readonly class AddToCart
{
public function __construct(
public OrderInterface $cart,
public ProductInterface $product,
public int $quantity,
) {
}
}
44 @application
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
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
final class LatestCartQuery
{
public function __construct(private OrderRepositoryInterface $orderRepository)
{
}
public function __invoke(): OrderInterface
{
return $this->orderRepository->findLatestCart();
}
}
@application
47
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
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
- SyliusBehatContextApplicationCartContext
filters:
tags: "@shopping_cart && @application"
50
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
- SyliusBehatContextApplicationCartContext
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
- SyliusBehatContextApplicationCartContext
51
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
- SyliusBehatContextApplicationCartContext
filters:
tags: "@shopping_cart && @application"
tags: "@shopping_cart && @application"
52
default:
suites:
api_v1_shopping_cart:
contexts:
- SyliusBehatContextApiV1CartContext
filters:
tags: "@shopping_cart && @api && @v1”
api_v2_shopping_cart:
contexts:
- SyliusBehatContextApiV2CartContext
filters:
tags: "@shopping_cart && @api && @v2"
53
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
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();
}
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();
}
?
? Fun
57
$this->shopClient->executeCustomRequest($request);
$this->adminClient->index(Resources::PRODUCT_VARIANTS);
$this->client->index();
<service id="sylius.behat.api_platform_client.admin.exchange_rate"
class="SyliusBehatClientApiPlatformClient" parent="sylius.behat.api_platform_client">
<argument>exchange-rates</argument>
<argument>admin</argument>
</service>
<service id=„sylius.behat.context.api.admin.managing_exchange_rates"
class="SyliusBehatContextApiAdminManagingExchangeRatesContext">
<argument type="service" id="sylius.behat.api_platform_client.admin.exchange_rate" />
...
</service>
58
59
60
61
Substitutability
Reusability
Fun
62
63
WHY? AND WHAT FOR?
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
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
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"
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"
@mpzalewski
Zales0123
mpzalewski.com
68
THANK YOU
QR CODE
@CommerceWeavers
@Sylius QR CODE
@mpzalewski Zales0123

More Related Content

Similar to [PHPers Summit 2023] Business logic testing

[ForumPHP 2023] Lights and shadows of BDD in Sylius (and probably other compa...
[ForumPHP 2023] Lights and shadows of BDD in Sylius (and probably other compa...[ForumPHP 2023] Lights and shadows of BDD in Sylius (and probably other compa...
[ForumPHP 2023] Lights and shadows of BDD in Sylius (and probably other compa...
Mateusz Zalewski
 
Zero to SOLID
Zero to SOLIDZero to SOLID
Zero to SOLID
Vic Metcalfe
 
Symfony World - Symfony components and design patterns
Symfony World - Symfony components and design patternsSymfony World - Symfony components and design patterns
Symfony World - Symfony components and design patterns
Łukasz Chruściel
 
Optimizing Magento by Preloading Data
Optimizing Magento by Preloading DataOptimizing Magento by Preloading Data
Optimizing Magento by Preloading Data
Ivan Chepurnyi
 
APIs for catalogs
APIs for catalogsAPIs for catalogs
APIs for catalogs
X.commerce
 
50 Laravel Tricks in 50 Minutes
50 Laravel Tricks in 50 Minutes50 Laravel Tricks in 50 Minutes
50 Laravel Tricks in 50 Minutes
Azim Kurt
 
laravel tricks in 50minutes
laravel tricks in 50minuteslaravel tricks in 50minutes
laravel tricks in 50minutes
Barang CK
 
Write a program that mimics the operations of several vending machin.pdf
Write a program that mimics the operations of several vending machin.pdfWrite a program that mimics the operations of several vending machin.pdf
Write a program that mimics the operations of several vending machin.pdf
eyebolloptics
 
[PHPCon 2023] Blaski i cienie BDD
[PHPCon 2023] Blaski i cienie BDD[PHPCon 2023] Blaski i cienie BDD
[PHPCon 2023] Blaski i cienie BDD
Mateusz Zalewski
 
Practical Event Sourcing
Practical Event SourcingPractical Event Sourcing
Practical Event Sourcing
Mathias Verraes
 
Getting started with ExtBase
Getting started with ExtBaseGetting started with ExtBase
Getting started with ExtBase
schmutt
 
I finished most of the program, but having trouble with some key fea.pdf
I finished most of the program, but having trouble with some key fea.pdfI finished most of the program, but having trouble with some key fea.pdf
I finished most of the program, but having trouble with some key fea.pdf
hardjasonoco14599
 
Technology and Science News - ABC News
Technology and Science News - ABC NewsTechnology and Science News - ABC News
Technology and Science News - ABC News
ignorantlogic4950
 
CodingSerbia2014-JavaVSPig
CodingSerbia2014-JavaVSPigCodingSerbia2014-JavaVSPig
CodingSerbia2014-JavaVSPig
Dusan Zamurovic
 
Symfony day 2016
Symfony day 2016Symfony day 2016
Symfony day 2016
Samuele Lilli
 
c++main.cpp#include iostream#include store.h#includ.docx
c++main.cpp#include iostream#include store.h#includ.docxc++main.cpp#include iostream#include store.h#includ.docx
c++main.cpp#include iostream#include store.h#includ.docx
humphrieskalyn
 
Wordpress plugin development from Scratch
Wordpress plugin development from ScratchWordpress plugin development from Scratch
Wordpress plugin development from Scratch
Ocaka Alfred
 
Oop php 5
Oop php 5Oop php 5
Oop php 5
phpubl
 
Backbone - TDC 2011 Floripa
Backbone - TDC 2011 FloripaBackbone - TDC 2011 Floripa
Backbone - TDC 2011 Floripa
Rafael Felix da Silva
 
Implement rich snippets in your webshop
Implement rich snippets in your webshopImplement rich snippets in your webshop
Implement rich snippets in your webshop
Arjen Miedema
 

Similar to [PHPers Summit 2023] Business logic testing (20)

[ForumPHP 2023] Lights and shadows of BDD in Sylius (and probably other compa...
[ForumPHP 2023] Lights and shadows of BDD in Sylius (and probably other compa...[ForumPHP 2023] Lights and shadows of BDD in Sylius (and probably other compa...
[ForumPHP 2023] Lights and shadows of BDD in Sylius (and probably other compa...
 
Zero to SOLID
Zero to SOLIDZero to SOLID
Zero to SOLID
 
Symfony World - Symfony components and design patterns
Symfony World - Symfony components and design patternsSymfony World - Symfony components and design patterns
Symfony World - Symfony components and design patterns
 
Optimizing Magento by Preloading Data
Optimizing Magento by Preloading DataOptimizing Magento by Preloading Data
Optimizing Magento by Preloading Data
 
APIs for catalogs
APIs for catalogsAPIs for catalogs
APIs for catalogs
 
50 Laravel Tricks in 50 Minutes
50 Laravel Tricks in 50 Minutes50 Laravel Tricks in 50 Minutes
50 Laravel Tricks in 50 Minutes
 
laravel tricks in 50minutes
laravel tricks in 50minuteslaravel tricks in 50minutes
laravel tricks in 50minutes
 
Write a program that mimics the operations of several vending machin.pdf
Write a program that mimics the operations of several vending machin.pdfWrite a program that mimics the operations of several vending machin.pdf
Write a program that mimics the operations of several vending machin.pdf
 
[PHPCon 2023] Blaski i cienie BDD
[PHPCon 2023] Blaski i cienie BDD[PHPCon 2023] Blaski i cienie BDD
[PHPCon 2023] Blaski i cienie BDD
 
Practical Event Sourcing
Practical Event SourcingPractical Event Sourcing
Practical Event Sourcing
 
Getting started with ExtBase
Getting started with ExtBaseGetting started with ExtBase
Getting started with ExtBase
 
I finished most of the program, but having trouble with some key fea.pdf
I finished most of the program, but having trouble with some key fea.pdfI finished most of the program, but having trouble with some key fea.pdf
I finished most of the program, but having trouble with some key fea.pdf
 
Technology and Science News - ABC News
Technology and Science News - ABC NewsTechnology and Science News - ABC News
Technology and Science News - ABC News
 
CodingSerbia2014-JavaVSPig
CodingSerbia2014-JavaVSPigCodingSerbia2014-JavaVSPig
CodingSerbia2014-JavaVSPig
 
Symfony day 2016
Symfony day 2016Symfony day 2016
Symfony day 2016
 
c++main.cpp#include iostream#include store.h#includ.docx
c++main.cpp#include iostream#include store.h#includ.docxc++main.cpp#include iostream#include store.h#includ.docx
c++main.cpp#include iostream#include store.h#includ.docx
 
Wordpress plugin development from Scratch
Wordpress plugin development from ScratchWordpress plugin development from Scratch
Wordpress plugin development from Scratch
 
Oop php 5
Oop php 5Oop php 5
Oop php 5
 
Backbone - TDC 2011 Floripa
Backbone - TDC 2011 FloripaBackbone - TDC 2011 Floripa
Backbone - TDC 2011 Floripa
 
Implement rich snippets in your webshop
Implement rich snippets in your webshopImplement rich snippets in your webshop
Implement rich snippets in your webshop
 

Recently uploaded

E-commerce Application Development Company.pdf
E-commerce Application Development Company.pdfE-commerce Application Development Company.pdf
E-commerce Application Development Company.pdf
Hornet Dynamics
 
Need for Speed: Removing speed bumps from your Symfony projects ⚡️
Need for Speed: Removing speed bumps from your Symfony projects ⚡️Need for Speed: Removing speed bumps from your Symfony projects ⚡️
Need for Speed: Removing speed bumps from your Symfony projects ⚡️
Łukasz Chruściel
 
OpenMetadata Community Meeting - 5th June 2024
OpenMetadata Community Meeting - 5th June 2024OpenMetadata Community Meeting - 5th June 2024
OpenMetadata Community Meeting - 5th June 2024
OpenMetadata
 
A Sighting of filterA in Typelevel Rite of Passage
A Sighting of filterA in Typelevel Rite of PassageA Sighting of filterA in Typelevel Rite of Passage
A Sighting of filterA in Typelevel Rite of Passage
Philip Schwarz
 
LORRAINE ANDREI_LEQUIGAN_HOW TO USE ZOOM
LORRAINE ANDREI_LEQUIGAN_HOW TO USE ZOOMLORRAINE ANDREI_LEQUIGAN_HOW TO USE ZOOM
LORRAINE ANDREI_LEQUIGAN_HOW TO USE ZOOM
lorraineandreiamcidl
 
Automated software refactoring with OpenRewrite and Generative AI.pptx.pdf
Automated software refactoring with OpenRewrite and Generative AI.pptx.pdfAutomated software refactoring with OpenRewrite and Generative AI.pptx.pdf
Automated software refactoring with OpenRewrite and Generative AI.pptx.pdf
timtebeek1
 
Vitthal Shirke Java Microservices Resume.pdf
Vitthal Shirke Java Microservices Resume.pdfVitthal Shirke Java Microservices Resume.pdf
Vitthal Shirke Java Microservices Resume.pdf
Vitthal Shirke
 
Custom Healthcare Software for Managing Chronic Conditions and Remote Patient...
Custom Healthcare Software for Managing Chronic Conditions and Remote Patient...Custom Healthcare Software for Managing Chronic Conditions and Remote Patient...
Custom Healthcare Software for Managing Chronic Conditions and Remote Patient...
Mind IT Systems
 
Launch Your Streaming Platforms in Minutes
Launch Your Streaming Platforms in MinutesLaunch Your Streaming Platforms in Minutes
Launch Your Streaming Platforms in Minutes
Roshan Dwivedi
 
Fundamentals of Programming and Language Processors
Fundamentals of Programming and Language ProcessorsFundamentals of Programming and Language Processors
Fundamentals of Programming and Language Processors
Rakesh Kumar R
 
A Study of Variable-Role-based Feature Enrichment in Neural Models of Code
A Study of Variable-Role-based Feature Enrichment in Neural Models of CodeA Study of Variable-Role-based Feature Enrichment in Neural Models of Code
A Study of Variable-Role-based Feature Enrichment in Neural Models of Code
Aftab Hussain
 
Top Features to Include in Your Winzo Clone App for Business Growth (4).pptx
Top Features to Include in Your Winzo Clone App for Business Growth (4).pptxTop Features to Include in Your Winzo Clone App for Business Growth (4).pptx
Top Features to Include in Your Winzo Clone App for Business Growth (4).pptx
rickgrimesss22
 
2024 eCommerceDays Toulouse - Sylius 2.0.pdf
2024 eCommerceDays Toulouse - Sylius 2.0.pdf2024 eCommerceDays Toulouse - Sylius 2.0.pdf
2024 eCommerceDays Toulouse - Sylius 2.0.pdf
Łukasz Chruściel
 
AI Pilot Review: The World’s First Virtual Assistant Marketing Suite
AI Pilot Review: The World’s First Virtual Assistant Marketing SuiteAI Pilot Review: The World’s First Virtual Assistant Marketing Suite
AI Pilot Review: The World’s First Virtual Assistant Marketing Suite
Google
 
Cracking the code review at SpringIO 2024
Cracking the code review at SpringIO 2024Cracking the code review at SpringIO 2024
Cracking the code review at SpringIO 2024
Paco van Beckhoven
 
Mobile App Development Company In Noida | Drona Infotech
Mobile App Development Company In Noida | Drona InfotechMobile App Development Company In Noida | Drona Infotech
Mobile App Development Company In Noida | Drona Infotech
Drona Infotech
 
May Marketo Masterclass, London MUG May 22 2024.pdf
May Marketo Masterclass, London MUG May 22 2024.pdfMay Marketo Masterclass, London MUG May 22 2024.pdf
May Marketo Masterclass, London MUG May 22 2024.pdf
Adele Miller
 
GOING AOT WITH GRAALVM FOR SPRING BOOT (SPRING IO)
GOING AOT WITH GRAALVM FOR  SPRING BOOT (SPRING IO)GOING AOT WITH GRAALVM FOR  SPRING BOOT (SPRING IO)
GOING AOT WITH GRAALVM FOR SPRING BOOT (SPRING IO)
Alina Yurenko
 
Transform Your Communication with Cloud-Based IVR Solutions
Transform Your Communication with Cloud-Based IVR SolutionsTransform Your Communication with Cloud-Based IVR Solutions
Transform Your Communication with Cloud-Based IVR Solutions
TheSMSPoint
 
GraphSummit Paris - The art of the possible with Graph Technology
GraphSummit Paris - The art of the possible with Graph TechnologyGraphSummit Paris - The art of the possible with Graph Technology
GraphSummit Paris - The art of the possible with Graph Technology
Neo4j
 

Recently uploaded (20)

E-commerce Application Development Company.pdf
E-commerce Application Development Company.pdfE-commerce Application Development Company.pdf
E-commerce Application Development Company.pdf
 
Need for Speed: Removing speed bumps from your Symfony projects ⚡️
Need for Speed: Removing speed bumps from your Symfony projects ⚡️Need for Speed: Removing speed bumps from your Symfony projects ⚡️
Need for Speed: Removing speed bumps from your Symfony projects ⚡️
 
OpenMetadata Community Meeting - 5th June 2024
OpenMetadata Community Meeting - 5th June 2024OpenMetadata Community Meeting - 5th June 2024
OpenMetadata Community Meeting - 5th June 2024
 
A Sighting of filterA in Typelevel Rite of Passage
A Sighting of filterA in Typelevel Rite of PassageA Sighting of filterA in Typelevel Rite of Passage
A Sighting of filterA in Typelevel Rite of Passage
 
LORRAINE ANDREI_LEQUIGAN_HOW TO USE ZOOM
LORRAINE ANDREI_LEQUIGAN_HOW TO USE ZOOMLORRAINE ANDREI_LEQUIGAN_HOW TO USE ZOOM
LORRAINE ANDREI_LEQUIGAN_HOW TO USE ZOOM
 
Automated software refactoring with OpenRewrite and Generative AI.pptx.pdf
Automated software refactoring with OpenRewrite and Generative AI.pptx.pdfAutomated software refactoring with OpenRewrite and Generative AI.pptx.pdf
Automated software refactoring with OpenRewrite and Generative AI.pptx.pdf
 
Vitthal Shirke Java Microservices Resume.pdf
Vitthal Shirke Java Microservices Resume.pdfVitthal Shirke Java Microservices Resume.pdf
Vitthal Shirke Java Microservices Resume.pdf
 
Custom Healthcare Software for Managing Chronic Conditions and Remote Patient...
Custom Healthcare Software for Managing Chronic Conditions and Remote Patient...Custom Healthcare Software for Managing Chronic Conditions and Remote Patient...
Custom Healthcare Software for Managing Chronic Conditions and Remote Patient...
 
Launch Your Streaming Platforms in Minutes
Launch Your Streaming Platforms in MinutesLaunch Your Streaming Platforms in Minutes
Launch Your Streaming Platforms in Minutes
 
Fundamentals of Programming and Language Processors
Fundamentals of Programming and Language ProcessorsFundamentals of Programming and Language Processors
Fundamentals of Programming and Language Processors
 
A Study of Variable-Role-based Feature Enrichment in Neural Models of Code
A Study of Variable-Role-based Feature Enrichment in Neural Models of CodeA Study of Variable-Role-based Feature Enrichment in Neural Models of Code
A Study of Variable-Role-based Feature Enrichment in Neural Models of Code
 
Top Features to Include in Your Winzo Clone App for Business Growth (4).pptx
Top Features to Include in Your Winzo Clone App for Business Growth (4).pptxTop Features to Include in Your Winzo Clone App for Business Growth (4).pptx
Top Features to Include in Your Winzo Clone App for Business Growth (4).pptx
 
2024 eCommerceDays Toulouse - Sylius 2.0.pdf
2024 eCommerceDays Toulouse - Sylius 2.0.pdf2024 eCommerceDays Toulouse - Sylius 2.0.pdf
2024 eCommerceDays Toulouse - Sylius 2.0.pdf
 
AI Pilot Review: The World’s First Virtual Assistant Marketing Suite
AI Pilot Review: The World’s First Virtual Assistant Marketing SuiteAI Pilot Review: The World’s First Virtual Assistant Marketing Suite
AI Pilot Review: The World’s First Virtual Assistant Marketing Suite
 
Cracking the code review at SpringIO 2024
Cracking the code review at SpringIO 2024Cracking the code review at SpringIO 2024
Cracking the code review at SpringIO 2024
 
Mobile App Development Company In Noida | Drona Infotech
Mobile App Development Company In Noida | Drona InfotechMobile App Development Company In Noida | Drona Infotech
Mobile App Development Company In Noida | Drona Infotech
 
May Marketo Masterclass, London MUG May 22 2024.pdf
May Marketo Masterclass, London MUG May 22 2024.pdfMay Marketo Masterclass, London MUG May 22 2024.pdf
May Marketo Masterclass, London MUG May 22 2024.pdf
 
GOING AOT WITH GRAALVM FOR SPRING BOOT (SPRING IO)
GOING AOT WITH GRAALVM FOR  SPRING BOOT (SPRING IO)GOING AOT WITH GRAALVM FOR  SPRING BOOT (SPRING IO)
GOING AOT WITH GRAALVM FOR SPRING BOOT (SPRING IO)
 
Transform Your Communication with Cloud-Based IVR Solutions
Transform Your Communication with Cloud-Based IVR SolutionsTransform Your Communication with Cloud-Based IVR Solutions
Transform Your Communication with Cloud-Based IVR Solutions
 
GraphSummit Paris - The art of the possible with Graph Technology
GraphSummit Paris - The art of the possible with Graph TechnologyGraphSummit Paris - The art of the possible with Graph Technology
GraphSummit Paris - The art of the possible with Graph Technology
 

[PHPers Summit 2023] Business logic testing

  • 1. WITH BEHAT, UI AND API BUSINESS LOGIC TESTING @mpzalewski Zales0123 MATEUSZ ZALEWSKI
  • 2. 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
  • 3. 3
  • 4. 4
  • 5. 5
  • 8. 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
  • 9. 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
  • 10. 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
  • 11. 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
  • 12. 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
  • 13. 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
  • 14. 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
  • 15. /** * @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
  • 16. 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
  • 17. 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
  • 18. /** * @Transform /^(?:this|that|the) ([^"]+)$/ */ public function getResource(mixed $resource): mixed { return $this->sharedStorage->get( StringInflector::nameToCode($resource) ); } 18
  • 19. 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
  • 20. 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
  • 21. $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
  • 22. $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
  • 23. 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
  • 24. $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
  • 26. 26
  • 27. 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
  • 28. 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
  • 29. 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
  • 30. 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
  • 31. 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
  • 32. 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
  • 33. <itemOperation name="shop_add_item"> <attribute name="method">POST</attribute> <attribute name="path">/shop/orders/{tokenValue}/items</attribute> <attribute name="messenger">input</attribute> <attribute name=„input">SyliusBundleApiBundleCommandCartAddItemToCart</attribute> <attribute name="normalization_context"> <attribute name="groups">shop:cart:read</attribute> </attribute> <attribute name="denormalization_context"> <attribute name="groups">shop:cart:add_item</attribute> </attribute> <attribute name="openapi_context"> <attribute name="summary">Adds Item to cart</attribute> </attribute> </itemOperation> @api 33
  • 34. 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
  • 35. 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
  • 36. 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
  • 37. { "@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
  • 38. SCENARIO UI CONTEXT PAGE UI API CONTEXT CLIENT Reusability 38
  • 39. 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
  • 40. 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
  • 41. 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
  • 42. 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
  • 43. 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
  • 44. final readonly class AddToCart { public function __construct( public OrderInterface $cart, public ProductInterface $product, public int $quantity, ) { } } 44 @application
  • 45. 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
  • 46. 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
  • 47. final class LatestCartQuery { public function __construct(private OrderRepositoryInterface $orderRepository) { } public function __invoke(): OrderInterface { return $this->orderRepository->findLatestCart(); } } @application 47
  • 48. 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
  • 49. 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 - SyliusBehatContextApplicationCartContext filters: tags: "@shopping_cart && @application" 50
  • 50. 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 - SyliusBehatContextApplicationCartContext 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 - SyliusBehatContextApplicationCartContext 51
  • 51. 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 - SyliusBehatContextApplicationCartContext filters: tags: "@shopping_cart && @application" tags: "@shopping_cart && @application" 52
  • 52. default: suites: api_v1_shopping_cart: contexts: - SyliusBehatContextApiV1CartContext filters: tags: "@shopping_cart && @api && @v1” api_v2_shopping_cart: contexts: - SyliusBehatContextApiV2CartContext filters: tags: "@shopping_cart && @api && @v2" 53
  • 53. 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
  • 54. 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(); }
  • 55. 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(); } ?
  • 58. 59
  • 59. 60
  • 60. 61
  • 63. 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
  • 64. 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
  • 65. 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"
  • 66. 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"
  • 68. THANK YOU QR CODE @CommerceWeavers @Sylius QR CODE @mpzalewski Zales0123