Share
Embed
Write more expressive tests with Hamcrest PHP
Talk given at the PHPNW 2016 uncon
Gareth Ellis
October 01, 2016
Transcript
26 slides
-
Matcher library originally written in Java, ported to PHP Allows
for more expressive assertions in your code -
$food = $data_source->getFood(); $fruit = [ 'orange', 'apple', 'pear', ];
$numberOfFruits = count($fruit); $this->assertEquals(0, count(array_diff($food, $fruit))); $this->assertEquals(0, count(array_diff($fruit, $food))); assertThat( $food, both(containsInAnyOrder($fruit)->and(is(arrayWithSize($numberOfFruits)) ); Real-world example -
$ 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 -
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” ] } } -
$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? -
$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 -
class Cake2ControllerRedirectMatcher extends \Hamcrest\BaseMatcher { private $url; public function __construct($url)
{ $this->url = $url; } public function matches($controller) { } public function describeTo(Description $description) { } } -
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; } } -
assertThat(‘foo’, is(intValue())); PHP Fatal error: Uncaught Hamcrest\AssertionError: Expected: is an
integer but: was a string "foo" -
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); } } -
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”]); } } } -
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 } -
$array = [‘Hello PHPNW16’, ‘Hello Gareth’, ‘Goodbye :-(‘]; $filtered =
array_filter($array, startsWith(‘Hello’)); //[‘Hello PHPNW16’, ‘Hello Gareth’] Matchers as callables