diff --git a/features/context.feature b/features/context.feature index 107e1c7..5d63af5 100644 --- a/features/context.feature +++ b/features/context.feature @@ -26,6 +26,13 @@ Feature: client aware context public function theClientShouldBeSet() { PHPUnit_Framework_Assert::assertInstanceOf('GuzzleHttp\Client', $this->client); } + + /** + * @Then the client default option :option should be equal to true + */ + public function theClientDefaultOptionShouldBeEqualToTrue($option) { + PHPUnit_Framework_Assert::assertSame(true, $this->client->getDefaultOption($option)); + } } """ @@ -34,7 +41,9 @@ Feature: client aware context """ default: extensions: - Behat\WebApiExtension: ~ + Behat\WebApiExtension: + defaults: + debug: true """ And a file named "features/client.feature" with: """ @@ -45,12 +54,13 @@ Feature: client aware context Scenario: client is set Then the client should be set + And the client default option "debug" should be equal to true + """ + When I run "behat -f progress features/client.feature" + Then it should pass with: """ - When I run "behat -f progress features/client.feature" - Then it should pass with: - """ - . + .. - 1 scenario (1 passed) - 1 step (1 passed) - """ + 1 scenario (1 passed) + 2 steps (2 passed) + """ diff --git a/features/testapp.feature b/features/testapp.feature index 2c9444e..adb24e4 100644 --- a/features/testapp.feature +++ b/features/testapp.feature @@ -39,6 +39,19 @@ Feature: Test app verification "pass": "pass" } ''' + + Scenario: + When I send a POST request to "echo" with form data: + | name | name | + | pass | pass | + Then the response should contain "POST" + And the response should contain json: + ''' + { + "name" : "name", + "pass": "pass" + } + ''' """ When I run "behat features/send_form.feature" Then it should pass with: @@ -204,6 +217,58 @@ Feature: Test app verification 1 scenario (1 passed) """ + Scenario: Asserting on the headers + Given a file named "features/assert_headers.feature" with: + """ + Feature: Exercise WebApiContext Assert Header + In order to validate the assert_header step + As a context developer + I need to be able to assert on the content of response headers + + Scenario: + When I send a GET request to "echo" + Then the response header "Content-Type" should be equal to "application/json" + Then the response header "Content-Type" should contain "json" + Then the response header "Content-Type" should not contain "magic" + Then the response header "Content-Type" should not be equal to "application" + """ + When I run "behat features/assert_headers.feature" + Then it should pass with: + """ + ..... + + 1 scenario (1 passed) + """ + + Scenario: Asserting on the body + Given a file named "features/assert_body.feature" with: + """ + Feature: Exercise WebApiContext Assert Body + In order to validate the assert body steps + As a context developer + I need to be able to assert on the content of the body + + Scenario: + When I send a GET request to "hello/Adrien" + Then the response should contain "Adrien" + And the response should not contain "foobar" + And the response should be equal to: + ''' + Hello Adrien + ''' + And the response should not be equal to: + ''' + Hello + ''' + """ + When I run "behat features/assert_body.feature" + Then it should pass with: + """ + ..... + + 1 scenario (1 passed) + """ + Scenario: Authentication Given a file named "features/authentication.feature" with: """ @@ -227,3 +292,34 @@ Feature: Test app verification 1 scenario (1 passed) """ + + Scenario: Redirects + Given a file named "features/redirects.feature" with: + """ + Feature: Exercise WebApiContext Redirects checking + In order to validate the redirect steps + As a context developer + I need to be able to detect and assert on redirects + + Scenario: + When I send a GET request to "redirect" with query parameters: + | url | /hello/christophe | + Then I should be redirected + Then I should be redirected to "/hello/christophe" + + Scenario: + Given I do not follow redirects + When I send a GET request to "redirect" with query parameters: + | url | /hello/christophe | + Then the response code should be 302 + And the response header "Location" should be equal to "/hello/christophe" + And I should be redirected + Then I should be redirected to "/hello/christophe" + """ + When I run "behat features/redirects.feature" + Then it should pass with: + """ + ......... + + 2 scenarios (2 passed) + """ diff --git a/src/Context/WebApiContext.php b/src/Context/WebApiContext.php index 1dbba68..9f32d34 100644 --- a/src/Context/WebApiContext.php +++ b/src/Context/WebApiContext.php @@ -14,6 +14,7 @@ use Behat\Gherkin\Node\TableNode; use GuzzleHttp\Client; use GuzzleHttp\Exception\RequestException; +use GuzzleHttp\Subscriber\History; use PHPUnit_Framework_Assert as Assertions; /** @@ -38,11 +39,21 @@ class WebApiContext implements ApiClientAwareContext */ private $headers = array(); + /** + * @var array + */ + private $requestOptions = array(); + /** * @var \GuzzleHttp\Message\RequestInterface */ private $request; + /** + * @var History + */ + private $requestHistory; + /** * @var \GuzzleHttp\Message\ResponseInterface */ @@ -97,10 +108,7 @@ public function iSetHeaderWithValue($name, $value) public function iSendARequest($method, $url) { $url = $this->prepareUrl($url); - $this->request = $this->client->createRequest($method, $url); - if (!empty($this->headers)) { - $this->request->addHeaders($this->headers); - } + $this->createRequest($method, $url); $this->sendRequest(); } @@ -126,11 +134,26 @@ public function iSendARequestWithValues($method, $url, TableNode $post) $bodyOption = array( 'body' => json_encode($fields), ); - $this->request = $this->client->createRequest($method, $url, $bodyOption); - if (!empty($this->headers)) { - $this->request->addHeaders($this->headers); + $this->createRequest($method, $url, $bodyOption); + + $this->sendRequest(); + } + + /** + * @When (I) send a :method request to :url with query parameters: + */ + public function iSendARequestWithQueryParameters($method, $url, TableNode $parameters) + { + $url = $this->prepareUrl($url); + $fields = array(); + + foreach ($parameters->getRowsHash() as $key => $val) { + $fields[$key] = $this->replacePlaceHolder($val); } + $this->createRequest($method, $url); + $this->request->setQuery($fields); + $this->sendRequest(); } @@ -148,7 +171,7 @@ public function iSendARequestWithBody($method, $url, PyStringNode $string) $url = $this->prepareUrl($url); $string = $this->replacePlaceHolder(trim($string)); - $this->request = $this->client->createRequest( + $this->createRequest( $method, $url, array( @@ -168,14 +191,31 @@ public function iSendARequestWithBody($method, $url, PyStringNode $string) * * @When /^(?:I )?send a ([A-Z]+) request to "([^"]+)" with form data:$/ */ - public function iSendARequestWithFormData($method, $url, PyStringNode $body) + public function iSendARequestWithFormData($method, $url, $body) { $url = $this->prepareUrl($url); - $body = $this->replacePlaceHolder(trim($body)); + + if ($body instanceof TableNode) { + $table = $body; + $body = array(); + foreach ($table->getRows() as $row) { + $body[] = sprintf( + '%s=%s', + $this->replacePlaceHolder($row[0]), + urlencode($this->replacePlaceHolder($row[1])) + ); + } + $body = join('&', $body); + } else { + $body = $this->replacePlaceHolder((string) $body); + } + + $body = trim($body); $fields = array(); parse_str(implode('&', explode("\n", $body)), $fields); - $this->request = $this->client->createRequest($method, $url); + + $this->createRequest($method, $url); /** @var \GuzzleHttp\Post\PostBodyInterface $requestBody */ $requestBody = $this->request->getBody(); foreach ($fields as $key => $value) { @@ -199,6 +239,56 @@ public function theResponseCodeShouldBe($code) Assertions::assertSame($expected, $actual); } + /** + * @Then (the) response header :header should be equal to :value + */ + public function theResponseHeaderShouldBeEqualTo($header, $value) + { + Assertions::assertSame($this->response->getHeader($header), $value); + } + + /** + * @Then (the) response header :header should not be equal to :value + */ + public function theResponseHeaderShouldNotBeEqualTo($header, $value) + { + Assertions::assertNotSame($this->response->getHeader($header), $value); + } + + /** + * @Then (the) response header :header should contain :value + */ + public function theResponseHeaderShouldContain($header, $value) + { + Assertions::assertContains($value, $this->response->getHeader($header)); + } + + /** + * @Then (the) response header :header should not contain :value + */ + public function theResponseHeaderShouldNotContain($header, $value) + { + Assertions::assertNotContains($value, $this->response->getHeader($header)); + } + + /** + * @Then (the) response should be equal to: + */ + public function theResponseShouldBeEqualTo(PyStringNode $text) + { + $actual = (string) $this->response->getBody(); + Assertions::assertSame($text->getRaw(), $actual); + } + + /** + * @Then (the) response should not be equal to: + */ + public function theResponseShouldNotBeEqualTo(PyStringNode $text) + { + $actual = (string) $this->response->getBody(); + Assertions::assertNotSame($text->getRaw(), $actual); + } + /** * Checks that response body contains specific text. * @@ -256,6 +346,37 @@ public function theResponseShouldContainJson(PyStringNode $jsonString) } } + /** + * @Given (I) do not follow redirects + */ + public function iDoNotFollowRedirects() + { + $this->requestOptions['allow_redirects'] = false; + } + + /** + * @Then (I) should be redirected + */ + public function iShouldBeRedirected() + { + $firstResponse = $this->getHistoryFirstResponse(); + $status = $firstResponse->getStatusCode(); + $isFirstResponseRedirect = $status >= 300 && $status < 400; + Assertions::assertTrue($isFirstResponseRedirect, sprintf('Response of status %d is not a redirect', $status)); + Assertions::assertArrayHasKey('Location', $firstResponse->getHeaders()); + } + + /** + * @Then (I) should be redirected to :to + */ + public function iShouldBeRedirectedTo($to) + { + $this->iShouldBeRedirected(); + + $firstResponse = $this->getHistoryFirstResponse(); + Assertions::assertSame($to, $firstResponse->getHeader('Location')); + } + /** * Prints last response body. * @@ -370,4 +491,32 @@ private function sendRequest() } } } + + private function createRequest($method, $url, $options = array()) + { + if (!empty($this->requestOptions)) { + $options = array_merge($this->requestOptions, $options); + } + + $this->request = $this->client->createRequest($method, $url, $options); + $this->request->getEmitter()->attach($this->requestHistory = new History()); + + if (!empty($this->headers)) { + $this->request->addHeaders($this->headers); + } + } + + private function getHistoryFirstResponse() + { + $historyIterator = $this->requestHistory->getIterator(); + $historyIterator->rewind(); + $firstTransaction = $historyIterator->current(); + $firstResponse = isset($firstTransaction['response']) ? $firstTransaction['response'] : null; + + if (null === $firstResponse) { + throw new \RuntimeException('No response found in the last request transaction log.'); + } + + return $firstResponse; + } } diff --git a/src/ServiceContainer/WebApiExtension.php b/src/ServiceContainer/WebApiExtension.php index 5418b78..cb43d3d 100644 --- a/src/ServiceContainer/WebApiExtension.php +++ b/src/ServiceContainer/WebApiExtension.php @@ -52,9 +52,12 @@ public function configure(ArrayNodeDefinition $builder) ->children() ->scalarNode('base_url') ->defaultValue('http://localhost') - ->end() ->end() - ->end(); + ->arrayNode('defaults') + ->prototype('variable')->end() + ->end() + ->end() + ; } /** diff --git a/testapp/index.php b/testapp/index.php index d5dd776..4000b60 100644 --- a/testapp/index.php +++ b/testapp/index.php @@ -2,6 +2,7 @@ use Silex\Application; use Symfony\Component\HttpFoundation\JsonResponse; +use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; require_once __DIR__ . '/../vendor/autoload.php'; @@ -50,5 +51,17 @@ function (Request $req) { return $response; } ); +$app->match( + 'hello/{name}', + function ($name) { + return 'Hello ' . $name; + } +); +$app->match( + 'redirect', + function (Request $req) { + return new RedirectResponse($req->get('url', '/hello/adrien')); + } +); $app->run();