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. Write more expressive tests with Hamcrest
    Gareth Ellis
    NomadPHP Lightning Talk, October 2016

    View Slide

  2. What is Hamcrest?

    View Slide

  3. Matcher library originally written in Java, ported to PHP
    Allows for more expressive assertions in your code

    View Slide

  4. PHPUnit:
    $this->assertEquals(“2”, $someValue);
    Hamcrest:
    assertThat($someValue, is(equalTo(“2”)));

    View Slide

  5. $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

    View Slide

  6. assertThat(3, is(greaterThan(2)));
    assertThat($someValue, is(atMost(99)));
    Number matchers!

    View Slide

  7. assertThat($someString, is(equalToIgnoringCase(“A sTrIng”)));
    assertThat($someString, containsString($subString));
    assertThat($someString, startsWith(“foo”));
    String matchers!

    View Slide

  8. assertThat($object, is(anInstanceOf(SomeClass::class)));
    assertThat(“string”, is(stringValue()));
    assertThat(123, is(not(stringValue())));
    Type matchers!

    View Slide

  9. assertThat($option, is(anyOf($arrayOfOptions)));
    assertThat($option, is(noneOf($arrayOfBlackListedOptions)));
    Inclusion/exclusion matchers!

    View Slide

  10. assertThat($array, contains($someValue));
    assertThat($array, is(arrayContainingInAnyOrder([“foo”, “bar”])));
    assertThat($array, is(arrayWithSize(4)));
    Array matchers!

    View Slide

  11. assertThat(“a string”, is(both(stringValue())->andAlso(not(emptyString())));
    Chained matchers!

    View Slide

  12. $ composer require hamcrest/hamcrest-php
    Usage

    View Slide

  13. $ composer require –dev graze/hamcrest-test-listener
    With PHPUnit (optional)
    In phpunit.xml:





    Allows PHPUnit to listen for
    Hamcrest assertions

    View Slide

  14. 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”
    ]
    }
    }

    View Slide

  15. Maximum expressiveness
    Custom matchers

    View Slide

  16. $redirectUrl = “/my_app/some_page?foo=bar”;
    $this->assertEquals(302, $controller->response->statusCode());
    $headers = $controller->response->header();
    $this->assertArrayHasKey($headers, “Location”);
    $this->assertEquals($headers[“Location”], $redirectUrl);
    Have you ever done anything like this in a test?

    View Slide

  17. $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

    View Slide

  18. class Cake2ControllerRedirectMatcher extends \Hamcrest\BaseMatcher
    {
    private $url;
    public function __construct($url)
    {
    $this->url = $url;
    }
    public function matches($controller)
    {
    }
    public function describeTo(Description $description)
    {
    }
    }

    View Slide

  19. 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;
    }
    }

    View Slide

  20. assertThat(‘foo’, is(intValue()));
    PHP Fatal error: Uncaught Hamcrest\AssertionError: Expected: is an
    integer
    but: was a string "foo"

    View Slide

  21. 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);
    }
    }

    View Slide

  22. 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”]);
    }
    }
    }

    View Slide

  23. function redirectedToUrl($url)
    {
    return new Cake2ControllerRedirectMatcher($url);
    }
    assertThat($controller, redirectedToUrl(“/some/url”));

    View Slide

  24. 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
    }

    View Slide

  25. $array = [‘Hello NomadPHP’, ‘Hello Gareth’, ‘Goodbye :-(‘];
    $filtered = array_filter($array, startsWith(‘Hello’));
    //[‘Hello NomadPHP’, ‘Hello Gareth’]
    Matchers as callables

    View Slide

  26. //Mockery
    $mock->shouldReceive(“some_method”)
    ->with(stringValue());
    //Phake
    Phake::verify($mock)->some_method(stringValue());
    Use with mocking libraries
    Both Phake and Mockery support using Hamcrest matchers for declaring
    expectations

    View Slide

  27. Thank you for listening
    @garethellis
    https://github.com/garethellis36/hamcrest-matchers

    View Slide