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

Write more expressive tests with Hamcrest

Write more expressive tests with Hamcrest

NomadPHP Lightning Talk, October 2016

Gareth Ellis

October 13, 2016
Tweet

More Decks by Gareth Ellis

Other Decks in Technology

Transcript

  1. Matcher library originally written in Java, ported to PHP Allows

    for more expressive assertions in your code
  2. $food = $data_source->getFood(); $fruit = [ 'orange', 'apple', 'pear', ];

    $this->assertEquals(0, count(array_diff($food, $fruit))); $this->assertEquals(0, count(array_diff($fruit, $food))); assertThat($food, containsOnlyValuesFrom($fruit)); Real-world example
  3. $ composer require –dev graze/hamcrest-test-listener With PHPUnit (optional) In phpunit.xml:

    <phpunit> <listeners> <listener class="\Hamcrest\Adapter\PHPUnit\TestListener"></listener> </listeners> </phpunit> Allows PHPUnit to listen for Hamcrest assertions
  4. require “vendors/hamcrest/hamcrest-php/hamcrest/Hamcrest.php”; Include Hamcrest’s global functions Or autoload via composer.json:

    { “autoload”: { “files”: [ “./vendors/hamcrest/hamcrest-php/hamcrest/Hamcrest.php” ] } }
  5. $redirectUrl = “/my_app/some_page?foo=bar”; assertThat($controller, redirectedToUrl($redirectUrl)); Wouldn’t it be much nicer

    to do this instead? assertThat() takes any value as its first argument… …and an instance of \Hamcrest \Matcher as its second argument Functions exist only as expressive factories for instantiating matchers
  6. class Cake2ControllerRedirectMatcher extends \Hamcrest\BaseMatcher { private $url; public function __construct($url)

    { $this->url = $url; } public function matches($controller) { } public function describeTo(Description $description) { } }
  7. class Cake2ControllerRedirectMatcher extends \Hamcrest\BaseMatcher { /* snip */ /** *

    @param Controller $controller * @return bool */ public function matches($controller) { assertThat($controller, is(anInstanceOf(Controller::class))); $headers = $controller->response->header(); if (!isset($headers["Location"])) { return false; } return $controller->response->statusCode() === 302 && $headers[“Location”] === $this->url; } }
  8. class Cake2ControllerRedirectMatcher extends \Hamcrest\BaseMatcher { /* snip */ public function

    describeTo(\Hamcrest\Description $description) { $description->appendText("Controller to have status code 302 and a header location containing "); $description->appendValue($this->url); } }
  9. class Cake2ControllerRedirectMatcher extends \Hamcrest\BaseMatcher { /* snip */ public function

    describeMismatch($controller, \Hamcrest\Description $description) { $description->appendText( “Controller response status code:” . $controller->response->statusCode() . “…” ); $headers = $controller->response->header(); if (!isset($headers[‘Location’])) { $this->appendText(“Location header has not been set”); } else { $description->appendText(“Location header is set as: “); $description->appendValue($headers[“Location”]); } } }
  10. function doSomethingToAString($string) { if (!is_string($string) { throw new InvalidArgumentException(“{$string} is

    not a string”); } //do something } Not just for tests – reduce boilerplate function doSomethingToAString($string) { assertThat($string, is(stringValue())); //throws \Hamcrest\AssertionError //do something }
  11. $array = [‘Hello NomadPHP’, ‘Hello Gareth’, ‘Goodbye :-(‘]; $filtered =

    array_filter($array, startsWith(‘Hello’)); //[‘Hello NomadPHP’, ‘Hello Gareth’] Matchers as callables