2. AGENDA
• Sylius in a nutshell
• Tests and their types
• PHPSpec usage for unit testing
• API testing with ApiTestCase
• New approach to Behat
• Summary
4. WHAT IS SYLIUS?
330+ contributors 10k+ contributions
5 years of development
Still in dev
A few revolutions behind us
60+ packages Full stack BDD
OSS Project
E-commerce platform
33. WHAT WASTHAT?
Uber Behat contexts
800+ lines of code
Many ancestors
Each Behat context got an `F` degree on scrutinizer
34. WAS IT WRONG?
/**
* @Given /^I am on the page of ([^""(w)]*) "([^""]*)"$/
* @Given /^I go to the page of ([^""(w)]*) "([^""]*)"$/
*/
public function iAmOnTheResourcePageByName($type, $name)
{
if ('country' === $type) {
$this->iAmOnTheCountryPageByName($name);
return;
}
$this->iAmOnTheResourcePage($type, 'name', $name);
}
38. @promotions
Feature: Checkout fixed discount promotions
In order to handle product promotions
As a store owner
I want to apply promotion discounts during checkout
Background:
Given store has default configuration
And the following countries exist:
| name |
| Germany |
| Poland |
And there are following taxonomies defined:
| name |
| Category |
And taxonomy "Category" has following taxons:
| Clothing > DebianT-Shirts |
And the following products exist:
| name | price | taxons |
| Woody | 125 | DebianT-Shirts |
And the following promotions exist:
| name | description |
| 3 items | Discount for orders with at least 3 items |
And all products are assigned to the default channel
And all promotions are assigned to the default channel
And promotion "3 items" has following rules defined:
| type | configuration |
| Item count | Count: 3,Equal: true |
And promotion "3 items" has following actions defined:
| type | configuration |
| Fixed discount | Amount: 15 |
And I am logged in as user "klaus@example.com"
Scenario: Fixed discount promotion is applied when the cart
has the required amount
Given I am on the store homepage
When I add product "Woody" to cart, with quantity "3"
Then I should be on the cart summary page
And "Promotion total: -€40.00" should appear on the page
And "Grand total: €295.00" should appear on the page
@receiving_discount
Feature: Receiving fixed discount on cart
In order to pay proper amount while buying promoted goods
As aVisitor
I want to have promotions applied to my cart
Background:
Given the store operates on a single channel in "France"
And the store has a product "PHPT-Shirt" priced at "€100.00"
And the store has a product "PHP Mug" priced at "€6.00"
@ui
Scenario: Receiving fixed discount for my cart
Given there is a promotion "Holiday promotion"
And it gives "€10.00" discount to every order
When I add product "PHPT-Shirt" to the cart
Then my cart total should be "€90.00"
And my discount should be "-€10.00"
39. @receiving_discount
Feature: Receiving percentage discount on shipping
In order to pay decreased amount for shipping
As a Customer
I want to have shipping promotion applied to my cart
Background:
Given the store operates on a single channel in "France"
And the store has "DHL" shipping method with "€10.00" fee
And the store has a product "PHPT-Shirt" priced at "€100.00"
And there is a promotion "Holiday promotion"
And I am a logged in customer
@ui
Scenario: Receiving percentage discount on shipping
Given the promotion gives "20%" discount on shipping to every order
When I add product "PHPT-Shirt" to the cart
And I proceed selecting "DHL" shipping method
Then my cart total should be "€108.00"
And my cart shipping total should be "€8.00"
41. TRANSFORMERS
<?php
final class PromotionContext implements Context
{
/**
* @Then promotion :promotion should still exist in the registry
*/
public function promotionShouldStillExistInTheRegistry(PromotionInterface $promotion)
{
Assert::notNull($this->promotionRepository->find($promotion->getId()));
}
}
<?php
final class PromotionContext implements Context
{
/**
* @Transform :promotion
*/
public function getPromotionByName($promotionName)
{
return $this->promotionRepository->findOneBy(['name' => $promotionName]);
}
}
@domain @ui
Scenario: Being unable to delete a promotion that was applied to an order
When I try to delete a "Christmas sale" promotion
Then I should be notified that it is in use and cannot be deleted
And promotion "Christmas sale" should still exist in the registry
42. TAGS
namespace SyliusBehatContextDomain;
final class OrderContext implements Context
{
/**
* @When I delete the order :order
*/
public function iDeleteTheOrder(OrderInterface $order)
{
$this->orderRepository->remove($order);
}
}
namespace SyliusBehatContextUi;
final class OrderContext implements Context
{
/**
* @When I delete the order :order
*/
public function iDeleteTheOrder(OrderInterface $order)
{
$this->showPage->open(['id' => $order->getId()]);
$this->showPage->deleteOrder();
}
}
@domain @ui
Scenario: Deleted order should disappear from the registry
When I delete the order "#00000022"
Then I should be notified that it has been successfully deleted
And this order should not exist in the registry
43. RECOMENDATIONS
Tags steps should be written in such a way
that can be interpreted in many contexts
Do not create a new object withTransformers
Shared storage container to keep data between contexts
Use ubiquitous language
45. PAGE AS A SERVICE
namespace SyliusBehatPageAdminAccount;
class LoginPage implements LoginPageInterface
{
public function specifyPassword($password)
{
$this->getDocument()->fillField('Password', $password);
}
public function specifyUsername($username)
{
$this->getDocument()->fillField('Username', $username);
}
}
namespace SyliusBehatContextUiAdmin;
final class LoginContext implements Context
{
public function __construct(LoginPageInterface $loginPage)
{
$this->loginPage = $loginPage;
}
/** @When I specify the username as :username */
public function iSpecifyTheUsername($username)
{
$this->loginPage->specifyUsername($username);
}
/** @When I specify the password as :password */
public function iSpecifyThePasswordAs($password)
{
$this->loginPage->specifyPassword($password);
}
}
46. CONTEXT AS A SERVICE
default:
suites:
ui_administrator_login:
contexts_as_services:
- sylius.behat.context.hook.doctrine_orm
- sylius.behat.context.transform.user
- sylius.behat.context.setup.channel
- sylius.behat.context.setup.admin_user
- sylius.behat.context.setup.user
- sylius.behat.context.ui.admin.login
filters:
tags: "@administrator_login && @ui"
namespace SyliusBehatContextUiAdmin;
final class LoginContext implements Context
{
public function __construct(LoginPageInterface $loginPage)
{
$this->loginPage = $loginPage;
}
/**
* @Given I want to log in
*/
public function iWantToLogIn(){…}
/**
* @When I specify the username as :username
*/
public function iSpecifyTheUsername($username){…}
/**
* @When I specify the password as :password
*/
public function iSpecifyThePasswordAs($password){…}
/**
* @When I log in
*/
public function iLogIn(){…}
}