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.

B3b2139e4f2c0eca4efe2379fcebc1c5?s=128

Anna Filina

March 07, 2018
Tweet

Transcript

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

    | Montreal
  2. • Project rescue expert. • Dev, mentor, public speaker. •

    Consultant at Zenika. Anna Filina
  3. Behavior: does it do what
 the user wants?

  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)); } }
  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
  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)
  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"
  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
  9. --- Expected +++ Actual @@ @@ "data": [ { "id":

    2, - "name": "Product 2", + "name": "WRONG DATA", "price": 10, "quantity": 2 },
  10. More than just
 JSON matching.

  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
  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
  13. Bug in Mobile App Items GET /api/users/1/cart Authorization: Bearer TOKEN_1

    Cart
  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
  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"
  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
  17. I need my API to go faster. How much faster?

    It takes 3s.
 Bring it under 0.5 Got it.
  18. Custom Steps

  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
  20. Given the fixtures file "expired_token.yml" is loaded 
 Then max

    3 queries were executes
 Then query time is under 0.05 seconds
  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 ); } }
  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(); }
  23. More than just
 REST APIs.

  24. None
  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
  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)
  27. Goutte Web Server Behat Behat
 Goutte Driver Started before tests

  28. But my JavaScript!

  29. Selenium Browser Behat Behat
 Selenium Driver Selenium Server Started before

    tests Web Server Browser's Driver
  30. composer require --dev \ behat/mink-selenium2-driver \
 se/selenium-server-standalone
 
 
 npm

    install chromedriver

  31. None
  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
  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)
  34. Browser tests are
 hardest to maintain.

  35. • Limit number of browser tests. • Test the JS

    using JS tooling. • Focus on critical path (checkout). Tips
  36. Behat can do
 unit tests, but...

  37. • Does the app do what the user wants? •

    Useful for "design by contract". • Security, performance, etc. • API, HTML & browser. Takeaways
  38. @afilina | afilina.com | youtube.com/c/AnnaFilina