Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Behat: Why and How?

Behat: Why and How?

Unit tests are great, but on their own, they don't tell you whether your application behaves as expected. Can a user access someone else's records? Do your translations work correctly based on request headers? Learn how to write relevant Behat tests, how to set everything up and how a backend and a frontend can be developed in parallel.

Anna Filina
PRO

March 07, 2018
Tweet

More Decks by Anna Filina

Other Decks in Programming

Transcript

  1. @afilina
    Behat: Why and How?
    ConFoo | March 7, 2018 | Montreal

    View Slide

  2. • Project rescue expert.
    • Dev, mentor, public speaker.
    • Consultant at Zenika.
    Anna Filina

    View Slide

  3. Behavior:
    does it do what

    the user wants?

    View Slide

  4. class CartTest extends TestCase
    {
    public function testGetTax()
    {
    $mock = $this->getMock('Cart',['getTaxRate']);
    $mock->expects($this->once())
    ->method('getTaxRate')
    ->will($this->returnValue(0.15));
    $this->assertEquals(15, $mock->getTax(100));
    }
    }

    View Slide

  5. Feature: Cart Items
    In order to let customers purchase products
    As a developer
    I need to add items to the shopping cart
    Background:
    Given the fixtures file "cart_items.yml" is loaded
    Scenario: List own cart items
    Given I send a "GET" request to "/api/users/1/cart"
    Then the response status code should be 200
    And the JSON node "data" should have 2 elements

    View Slide

  6. $ vendor/bin/behat
    Feature: Cart Items
    In order to let customers purchase products
    As a developer
    I need to add items to the shopping cart
    Background:
    Given the fixtures file "cart_items.yml" is loaded

    Scenario: List own cart items
    Given I send a "GET" request to "/api/users/1/cart"
    Then the response status code should be 200
    And the JSON node "data" should have 2 elements
    1 scenario (1 passed)
    4 steps (4 passed)
    0m0.31s (29.05Mb)

    View Slide

  7. Feature: Cart Items
    In order to let customers purchase products
    As a developer
    I need to add items to the shopping cart
    Background:
    Given the fixtures file "cart_items.yml" is loaded
    Scenario: List own cart items
    Given I send a "GET" request to "/api/users/1/cart"
    Then the response status code should be 200
    And the JSON should be equal to file "users_1_cart.json"

    View Slide

  8. /api/users/1/cart
    {
    "data": [
    {
    "id": 2,
    "name": "Product 2",
    "price": 10,
    "quantity": 2
    },
    {
    "id": 1,
    "name": "Product 1",
    "price": 10,
    "quantity": 2
    }
    ]
    }
    Backend dev Frontend dev

    View Slide

  9. --- Expected
    +++ Actual
    @@ @@
    "data": [
    {
    "id": 2,
    - "name": "Product 2",
    + "name": "WRONG DATA",
    "price": 10,
    "quantity": 2
    },

    View Slide

  10. More than just

    JSON matching.

    View Slide

  11. Scenario: Unauthenticated list
    Given I send a "GET" request to "/api/users/1/cart"
    Then the response status code should be 401
    Scenario: List own cart items
    Given I add "Authorization" header equal to "Bearer TOKEN_1"
    And I send a "GET" request to "/api/users/1/cart"
    Then the response status code should be 200

    View Slide

  12. Scenario: List another user's cart items
    Given I add "Authorization" header equal to "Bearer TOKEN_1"
    And I send a "GET" request to "/api/users/2/cart"
    Then the response status code should be 403

    View Slide

  13. Bug in Mobile App
    Items
    GET /api/users/1/cart
    Authorization: Bearer TOKEN_1
    Cart

    View Slide

  14. Background:
    Given the fixtures file "expired_token.yml" is loaded
    Scenario: Expired token
    Given I add "Authorization" header equal to "Bearer TOKEN_1"
    And I send a "GET" request to "/api/users/1/cart"
    Then the response status code should be 401

    View Slide

  15. Scenario: List cart items in French
    Given I add "Accept-Language" header equal to "fr-CA,en;q=0.9"

    And I send a "GET" request to "/api/users/1/cart"
    Then the response status code should be 200
    And the JSON should be equal to file "users_1_cart_fr.json"

    View Slide

  16. Scenario: List own cart items
    Given I add "Authorization" header equal to "Bearer TOKEN_1"
    And I send a "GET" request to "/api/users/1/cart"
    Then the response status code should be 200
    And max 3 queries were executes
    And query time is under 0.05 seconds

    View Slide

  17. I need my API
    to go faster.
    How much
    faster?
    It takes 3s.

    Bring it under 0.5
    Got it.

    View Slide

  18. Custom Steps

    View Slide

  19. Given I add "Authorization" header equal to "Bearer TOKEN_1"

    Given I send a "GET" request to "/api/users/1/cart"

    Then the response status code should be 200

    View Slide

  20. Given the fixtures file "expired_token.yml" is loaded

    Then max 3 queries were executes

    Then query time is under 0.05 seconds

    View Slide

  21. class JsonContext extends \Behatch\Context\JsonContext
    {
    /**
    * @Then the JSON should be equal to file :filename
    */
    public function theJsonShouldBeEqualToFile(string $filename):void
    {
    $actualJson = json_encode(
    $this->getJson()->getContent(),
    JSON_PRETTY_PRINT
    );
    $expectedJson = file_get_contents(
    __DIR__ . '/../../json/'.$filename
    );
    PHPUnit\Framework\Assert::assertJsonStringEqualsJsonString(
    $expectedJson,
    $actualJson
    );
    }
    }

    View Slide

  22. /**
    * @Given the fixtures file :filename is loaded
    */
    public function loadYamlFixtures(string $filename):void
    {
    $loader = new \Nelmio\Alice\Loader\NativeLoader();
    $objectSet = $loader->loadFile(__DIR__.'/../../fixtures/'.$filename);
    foreach ($objectSet->getObjects() as $object) {
    $className = get_class($object);
    $this->em->persist($object);
    }
    $this->em->flush();
    }

    View Slide

  23. More than just

    REST APIs.

    View Slide

  24. View Slide

  25. Feature: Product search
    Background:
    Given the fixtures file "products.yml" is loaded
    Scenario: Search existing products
    Given I am on "/products/Elder+Scrolls"
    Then the response status code should be 200
    And I should see 5 "div.product" elements

    View Slide

  26. Feature: Product search
    Background:
    Given the fixtures file "products.yml" is loaded
    Scenario: Search existing products
    Given I am on "/products/Elder+Scrolls"
    Then the response status code should be 200
    And I should see 5 "div.product" elements
    1 scenario (1 passed)
    4 steps (4 passed)
    0m0.28s (29.15Mb)

    View Slide

  27. Goutte
    Web Server
    Behat
    Behat

    Goutte Driver
    Started before tests

    View Slide

  28. But my
    JavaScript!

    View Slide

  29. Selenium
    Browser
    Behat
    Behat

    Selenium Driver
    Selenium Server
    Started before tests
    Web Server
    Browser's
    Driver

    View Slide

  30. composer require --dev \
    behat/mink-selenium2-driver \

    se/selenium-server-standalone



    npm install chromedriver


    View Slide

  31. View Slide

  32. @javascript
    Scenario: Filter products
    Given I am on "/products"
    When I fill in "search" with "path"
    Then I should see 1 "div.product.visible" element
    No :visible
    pseudoclass

    View Slide

  33. Feature: Product search
    Background:
    Given the fixtures file "products.yml" is loaded
    @javascript
    Scenario: Filter products
    Given I am on "/products"
    When I fill in "search" with "path"
    Then I should see 1 "div.product.visible" element
    1 scenario (1 passed)
    4 steps (4 passed)
    0m2.27s (20.53Mb)

    View Slide

  34. Browser tests are

    hardest to maintain.

    View Slide

  35. • Limit number of browser tests.
    • Test the JS using JS tooling.
    • Focus on critical path (checkout).
    Tips

    View Slide

  36. Behat can do

    unit tests, but...

    View Slide

  37. • Does the app do what the user wants?
    • Useful for "design by contract".
    • Security, performance, etc.
    • API, HTML & browser.
    Takeaways

    View Slide

  38. @afilina | afilina.com | youtube.com/c/AnnaFilina

    View Slide