[yesod-test] Adds requireJSONResponse function#1646
Conversation
This function checks that a response body is JSON, and parses it into a Haskell value. Having something like this function is pretty essential to using Yesod as a JSON API server, so I think it's a good addition. You can use it to parse a Haskell record directly (usually by adding FromJSON classes to your response types), or parse a Value and pull out individual fields, maybe using something like `aeson-lens` (though probably a testing-specific library would be better). I debated over these things: 1. The name. I was thinking of something like [assert/require/decode/parse]JSON[Response/Body]. I ultimately went with requireJSONResponse: - decode/parse sound like the aeson functions that return Either or Maybe, and I wanted this function to throw an error if it failed - I'm open to using `assertJSONResponse`—it matches the other functions (`assertEq`) better—but I think it reads less like English. - I chose Response over Body because (a) It also checks the content-type header, which is not in the body (b) "Body" felt slightly in-the-weeds of HTTP; I think "response" is more approachable. 2. Should it require the JSON content type? You can definitely have a server that returns JSON without JSON content types, but I think that's a such a bad idea, it's more likely requiring it helps people if they accidentally don't add the header. 3. Should it take a String parameter to add to the error message? This would match `assertEq`, but other functions like `statusIs` don't take a message. Ultimately I went without it, because the messages felt like I was repeating myself: `(comment :: Comment) <- requireJSONResponse "the response has a comment"`
|
This closes #1643 |
yesod-test/Yesod/Test.hs
Outdated
| isJSONContentType | ||
| (failure $ T.pack $ "Expected `Content-Type: application/json` in the headers, got: " ++ show headers) | ||
| case eitherDecode' body of | ||
| -- TODO: include full body in error message? |
There was a problem hiding this comment.
Could do this if there's a failure. I think it would mostly be quite useful, but would be large and sometimes extremely large (e.g. Base64 encoded images). Thoughts?
There was a problem hiding this comment.
How about including the first 256 (arbitrary number) bytes?
There was a problem hiding this comment.
This seems pretty reasonable
There was a problem hiding this comment.
Cool, implemented the body preview
yesod-test/Yesod/Test.hs
Outdated
| requireJSONResponse = do | ||
| withResponse $ \(SResponse _status headers body) -> do | ||
| let mContentType = lookup hContentType headers | ||
| isJSONContentType = maybe False (\contentType -> BS8.takeWhile (/= ';') contentType == "application/json") mContentType |
There was a problem hiding this comment.
Copied from acceptsJson, mostly
There was a problem hiding this comment.
Would it work to use acceptsJson here? That would be preferable, since the set of acceptable JSON mime types may expand in the future.
There was a problem hiding this comment.
acceptsJson is MonadHandler, so we couldn't just drop it in here I don't think, but I could create a new function exposed from yesod-core that takes a list of headers ([(CI ByteString, ByteString)], aka RequestHeaders) and returned if one of the was a JSON content type?
There was a problem hiding this comment.
Realized that the Accept header is slightly different, since it has a list of preferred MIME types. I added a contentTypeIsJson function for general use on Content-Type.
|
Ok I think this is ready for final review. One last thing is should the function be renamed |
snoyberg
left a comment
There was a problem hiding this comment.
LGTM! Want me to merge or release, or you want to?
|
I'll take the release 👍 |
(This was missing from #1646)
|
Done 👍 |
|
(note: this PR was missing a requirement from yesod-test that it was getting the latest yesod-core for the new content type check. I pushed that directly to master) |
This function checks that a response body is JSON, and parses it into a Haskell value. Having something like this function is pretty essential to using Yesod as a JSON API server, so I think it's a good addition. You can use it to parse a Haskell record directly (usually by adding FromJSON classes to your response types), or parse a Value and pull out individual fields, maybe using something like
aeson-lens(though probably a testing-specific library would be better).I debated over these things:
assertJSONResponse—it matches the other functions (assertEq) better—but I think it reads less like English.requireJsonBody. That kind of rules out that name to avoid duplicate identifier issues.assertEq, but other functions likestatusIsdon't take a message. Ultimately I went without it, because the messages felt like I was repeating myself:(comment :: Comment) <- requireJSONResponse "the response has a comment"Before submitting your PR, check that you've:
@sincedeclarations to the Haddocks for new, public APIsAfter submitting your PR: