If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeTime for me to reveal why I chose this error response format with type
, title
and errors
. Imagine if every JSON API returned the same format when things went wrong: always with these keys. That'd be pretty awesome. As API client, we'd always know what to expect and what things mean. That's a beautiful fairy tale.
In the real world, every API does whatever they want. But there are people out there working on standards for error responses, with the hope that someday, API's have some consistency.
One of those is called api problem details. Google for that. Ah, a boring spec document. Go ahead and read this whole thing, I'll wait... Kidding! I'll show you the good parts.
But first - click the draft ietf link. These drafts go through version, and this one has been replaced with a whole new document. And yea, these are just drafts - not official specs. But who cares? If a spec makes sense to you, why not follow it instead of making up your own format.
Scroll down to the example response. Two important things here. First, if you follow this spec, you should return a custom Content-type
response header to advertise this: application/problem+json
. When a client sees this - they can research it to find out what the fields in the response mean in human terms.
Second, check out the fields: the main ones are type
and title
. type
is a unique string for what went wrong. It's supposed to be a URL - our's is just a key. We'll revisit that later. Next, this says title
is a human-readable version of type
and there are a bunch of other optional fields. The Extension Members section says that you can also add whatever other fields you want. We're adding an extra errors
key.
So we're already following this format - or at least we're pretty close. So I want to advertise this to our clients so they can dig into what each key means. Copy the application/problem+json
Content-Type
header so we can use it.
First, check for this in the test: $this->assertEquals()
with application/problem+json
as the expected value and $response->getHeader('Content-Type')
for the actual value:
... lines 1 - 5 | |
class ProgrammerControllerTest extends ApiTestCase | |
{ | |
... lines 8 - 123 | |
public function testValidationErrors() | |
{ | |
... lines 126 - 144 | |
$this->assertEquals('application/problem+json', $response->getHeader('Content-Type')); | |
} | |
} |
Make sure it fails - run just this test:
bin/phpunit -c app --filter testValidationErrors
Yep, it fails. Now go to ProgrammerController
and find createValidationErrorResponse
. Instead of returning JsonResponse
, set it to a $response
variable and then call $response->headers->set()
with Content-Type
and application/problem+json
. Return this:
... lines 1 - 15 | |
class ProgrammerController extends BaseController | |
{ | |
... lines 18 - 165 | |
private function createValidationErrorResponse(FormInterface $form) | |
{ | |
... lines 168 - 175 | |
$response = new JsonResponse($data, 400); | |
$response->headers->set('Content-Type', 'application/problem+json'); | |
return $response; | |
} | |
} |
See if this fixed the test:
bin/phpunit -c app --filter testValidationErrors
Error!
Attempted to call function JsonResponse from namespace "AppBundle\Controller\Api"
That looks like a "Ryan" mistake - I deleted the new
keyword before JsonResponse
. You probably saw me do that. Put it back and now try the tests:
Beautiful green! And now we're advertising our special error format.
Ok I reply myself, I guess it's a Guzzle 6 update stuff... It seems that the getHeader('Content-Type') is an array. So I added [0] to fetch the first... It works. Sorry
Hey julien moulis
Don't worry ;) I'm glad that you could fix your problem.
Sometimes is handy to dump some variables inside your tests, so you can actually see what's going on.
Have a nice day
// composer.json
{
"require": {
"php": ">=5.3.3",
"symfony/symfony": "2.6.*", // v2.6.11
"doctrine/orm": "~2.2,>=2.2.3,<2.5", // v2.4.7
"doctrine/dbal": "<2.5", // v2.4.4
"doctrine/doctrine-bundle": "~1.2", // v1.4.0
"twig/extensions": "~1.0", // v1.2.0
"symfony/assetic-bundle": "~2.3", // v2.6.1
"symfony/swiftmailer-bundle": "~2.3", // v2.3.8
"symfony/monolog-bundle": "~2.4", // v2.7.1
"sensio/distribution-bundle": "~3.0,>=3.0.12", // v3.0.21
"sensio/framework-extra-bundle": "~3.0,>=3.0.2", // v3.0.7
"incenteev/composer-parameter-handler": "~2.0", // v2.1.0
"hautelook/alice-bundle": "0.2.*", // 0.2
"jms/serializer-bundle": "0.13.*" // 0.13.0
},
"require-dev": {
"sensio/generator-bundle": "~2.3", // v2.5.3
"behat/behat": "~3.0", // v3.0.15
"behat/mink-extension": "~2.0.1", // v2.0.1
"behat/mink-goutte-driver": "~1.1.0", // v1.1.0
"behat/mink-selenium2-driver": "~1.2.0", // v1.2.0
"phpunit/phpunit": "~4.6.0" // 4.6.4
}
}
Hi,
I have an error in my testValidationErrors. When I add the assert ```$this->assertEquals('application/problem+json', $response->getHeader('Content-Type'));```
My test fails with ```Array (...) does not match expected type "string".```
I'm not sure to understand why?
What do you think?
Thx Julien