diff --git a/composer.json b/composer.json index 8ff7a15..2819bac 100644 --- a/composer.json +++ b/composer.json @@ -19,13 +19,13 @@ "require": { "php": ">=5.4", "behat/behat": "~3.0", - "guzzlehttp/guzzle": "4 - 5", - "phpunit/phpunit": "~4.0" + "guzzlehttp/guzzle": "4 - 5" }, "require-dev": { "symfony/process": "~2.1", - "silex/silex": "~1" + "silex/silex": "~1", + "phpunit/phpunit": "~4.0" }, "autoload": { diff --git a/features/exceptions.feature b/features/exceptions.feature new file mode 100644 index 0000000..94590c6 --- /dev/null +++ b/features/exceptions.feature @@ -0,0 +1,193 @@ +Feature: Feature failures + In order to resolve problems easily + As a WebApi feature tester + I want clear error messages + + Background: + Given a file named "behat.yml" with: + """ + default: + formatters: + progress: ~ + extensions: + Behat\WebApiExtension: + base_url: http://localhost:8080/ + + suites: + default: + contexts: ['Behat\WebApiExtension\Context\WebApiContext'] + """ + + Scenario: Response code + Given a file named "features/authentication.feature" with: + """ + Feature: Accessing an invalid url + In order to known about my mistakes + As an API client + I should receive an error response + + Scenario: + When I send a GET request to "/404" + Then the response code should be 200 + """ + When I run "behat features/authentication.feature" + Then it should fail with: + """ + .F + + --- Failed steps: + + Then the response code should be 200 # features/authentication.feature:8 + The response code was 404, not 200 (Behat\WebApiExtension\Context\ExpectationException) + + 1 scenario (1 failed) + 2 steps (1 passed, 1 failed) + """ + + Scenario: Response contains + Given a file named "features/headers.feature" with: + """ + Feature: Exercise WebApiContext Set Header + In order to validate the set_header step + As a context developer + I need to be able to add headers in a scenario before sending a request + + Scenario: + When I send a GET request to "echo" + Then the response should contain "foo" + """ + When I run "behat features/headers.feature" + Then it should fail with: + """ + .F + + --- Failed steps: + + Then the response should contain "foo" # features/headers.feature:8 + Response body does not contain the specified text (Behat\WebApiExtension\Context\ExpectationException) + + 1 scenario (1 failed) + 2 steps (1 passed, 1 failed) + """ + + Scenario: Response does not contain + Given a file named "features/headers.feature" with: + """ + Feature: Exercise WebApiContext Set Header + In order to validate the set_header step + As a context developer + I need to be able to add headers in a scenario before sending a request + + Scenario: + Given I set header "foo" with value "bar" + When I send a GET request to "echo" + Then the response should not contain "foo" + """ + When I run "behat features/headers.feature" + Then it should fail with: + """ + ..F + + --- Failed steps: + + Then the response should not contain "foo" # features/headers.feature:9 + Response body contains the specified text (Behat\WebApiExtension\Context\ExpectationException) + + 1 scenario (1 failed) + 3 steps (2 passed, 1 failed) + """ + + Scenario: Response contains JSON (invalid JSON) + Given a file named "features/headers.feature" with: + """ + Feature: Exercise WebApiContext Set Header + In order to validate the set_header step + As a context developer + I need to be able to add headers in a scenario before sending a request + + Scenario: + When I send a GET request to "echo" + And the response should contain json: + ''' + foo + ''' + """ + When I run "behat features/headers.feature" + Then it should fail with: + """ + .F + + --- Failed steps: + + And the response should contain json: # features/headers.feature:8 + Can not convert etalon to json: + foo (LogicException) + + 1 scenario (1 failed) + 2 steps (1 passed, 1 failed) + """ + + Scenario: Response contains JSON (missing key) + Given a file named "features/headers.feature" with: + """ + Feature: Exercise WebApiContext Set Header + In order to validate the set_header step + As a context developer + I need to be able to add headers in a scenario before sending a request + + Scenario: + When I send a GET request to "echo" + And the response should contain json: + ''' + { + "foo" : "bar" + } + ''' + """ + When I run "behat features/headers.feature" + Then it should fail with: + """ + .F + + --- Failed steps: + + And the response should contain json: # features/headers.feature:8 + Does not contain the key "foo" (Behat\WebApiExtension\Context\ExpectationException) + + 1 scenario (1 failed) + 2 steps (1 passed, 1 failed) + """ + + Scenario: Response contains JSON + Given a file named "features/headers.feature" with: + """ + Feature: Exercise WebApiContext Set Header + In order to validate the set_header step + As a context developer + I need to be able to add headers in a scenario before sending a request + + Scenario: + When I send a POST request to "echo" with form data: + ''' + foo=bar + ''' + And the response should contain json: + ''' + { + "foo" : "baz" + } + ''' + """ + When I run "behat features/headers.feature" + Then it should fail with: + """ + .F + + --- Failed steps: + + And the response should contain json: # features/headers.feature:11 + Value for the key "foo" does not match (Behat\WebApiExtension\Context\ExpectationException) + + 1 scenario (1 failed) + 2 steps (1 passed, 1 failed) + """ diff --git a/src/Context/ExpectationException.php b/src/Context/ExpectationException.php new file mode 100644 index 0000000..519cb40 --- /dev/null +++ b/src/Context/ExpectationException.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Behat\WebApiExtension\Context; + +class ExpectationException extends \RuntimeException +{ + /** + * Initializes exception. + * + * @param string $message Message. + * @param \Exception $exception Expectation exception. + */ + public function __construct($message = '', \Exception $exception = null) + { + parent::__construct($message, 0, $exception); + } +} diff --git a/src/Context/WebApiContext.php b/src/Context/WebApiContext.php index f21b24b..c8f5b48 100644 --- a/src/Context/WebApiContext.php +++ b/src/Context/WebApiContext.php @@ -14,7 +14,6 @@ use Behat\Gherkin\Node\TableNode; use GuzzleHttp\ClientInterface; use GuzzleHttp\Exception\RequestException; -use PHPUnit_Framework_Assert as Assertions; /** * Provides web API description definitions. @@ -196,7 +195,7 @@ public function theResponseCodeShouldBe($code) { $expected = intval($code); $actual = intval($this->response->getStatusCode()); - Assertions::assertSame($expected, $actual); + $this->assertSame($expected, $actual, sprintf('The response code was %s, not %s', $actual, $expected)); } /** @@ -210,7 +209,7 @@ public function theResponseShouldContain($text) { $expectedRegexp = '/' . preg_quote($text) . '/i'; $actual = (string) $this->response->getBody(); - Assertions::assertRegExp($expectedRegexp, $actual); + $this->assertRegex($expectedRegexp, $actual, 'Response body does not contain the specified text'); } /** @@ -224,7 +223,12 @@ public function theResponseShouldNotContain($text) { $expectedRegexp = '/' . preg_quote($text) . '/'; $actual = (string) $this->response->getBody(); - Assertions::assertNotRegExp($expectedRegexp, $actual); + $this->assertNegative( + function () use ($expectedRegexp, $actual) { + $this->assertRegex($expectedRegexp, $actual); + }, + 'Response body contains the specified text' + ); } /** @@ -244,15 +248,13 @@ public function theResponseShouldContainJson(PyStringNode $jsonString) $actual = $this->response->json(); if (null === $etalon) { - throw new \RuntimeException( - "Can not convert etalon to json:\n" . $this->replacePlaceHolder($jsonString->getRaw()) + throw new \LogicException( + "Can not convert etalon to json:\n" . $this->replacePlaceHolder($jsonString->getRaw()) ); } - - Assertions::assertGreaterThanOrEqual(count($etalon), count($actual)); foreach ($etalon as $key => $needle) { - Assertions::assertArrayHasKey($key, $actual); - Assertions::assertEquals($etalon[$key], $actual[$key]); + $this->assertArrayHasKey($key, $actual, sprintf('Does not contain the key "%s"', $key)); + $this->assertEquals($etalon[$key], $actual[$key], sprintf('Value for the key "%s" does not match', $key)); } } @@ -379,4 +381,45 @@ private function getClient() return $this->client; } + + private function assertNegative($callable, $message = 'Passed') + { + try { + call_user_func($callable); + } catch (ExpectationException $e) { + return; + } + + throw new ExpectationException($message); + } + + private function assertEquals($expected, $actual, $message = 'Not equal') + { + if ($actual != $expected) { + throw new ExpectationException($message); + } + } + + private function assertSame($expected, $actual, $message = 'Not the same') + { + if ($actual !== $expected) { + throw new ExpectationException($message); + } + } + + private function assertArrayHasKey($key, array $array, $message = 'Does not have key') { + if (!array_key_exists($key, $array)) { + throw new ExpectationException($message); + } + } + + private function assertRegex($pattern, $subject, $message = 'Does not match') { + $result = preg_match($pattern, $subject); + + if (false === $result) { + throw new \LogicException('Invalid regular expression'); + } elseif (0 === $result) { + throw new ExpectationException($message); + } + } }