Safe Haskell | None |
---|---|
Language | Haskell98 |
Yesod.Test is a pragmatic framework for testing web applications built using wai and persistent.
By pragmatic I may also mean dirty
. Its main goal is to encourage integration
and system testing of web applications by making everything easy to test.
Your tests are like browser sessions that keep track of cookies and the last visited page. You can perform assertions on the content of HTML responses, using CSS selectors to explore the document more easily.
You can also easily build requests using forms present in the current page. This is very useful for testing web applications built in yesod, for example, where your forms may have field names generated by the framework or a randomly generated CSRF token input.
Your database is also directly available so you can use runDB
to set up
backend pre-conditions, or to assert that your session is having the desired effect.
- yesodSpec :: YesodDispatch site => site -> YesodSpec site -> Spec
- type YesodSpec site = Writer [YesodSpecTree site] ()
- yesodSpecWithSiteGenerator :: YesodDispatch site => IO site -> YesodSpec site -> Spec
- yesodSpecApp :: YesodDispatch site => site -> IO Application -> YesodSpec site -> Spec
- type YesodExample site = StateT (YesodExampleData site) IO
- data YesodExampleData site = YesodExampleData {
- yedApp :: !Application
- yedSite :: !site
- yedCookies :: !Cookies
- yedResponse :: !(Maybe SResponse)
- data YesodSpecTree site
- = YesodSpecGroup String [YesodSpecTree site]
- | YesodSpecItem String (YesodExample site ())
- ydescribe :: String -> YesodSpec site -> YesodSpec site
- yit :: String -> YesodExample site () -> YesodSpec site
- get :: (Yesod site, RedirectUrl site url) => url -> YesodExample site ()
- post :: (Yesod site, RedirectUrl site url) => url -> YesodExample site ()
- postBody :: (Yesod site, RedirectUrl site url) => url -> ByteString -> YesodExample site ()
- request :: Yesod site => RequestBuilder site () -> YesodExample site ()
- addRequestHeader :: Header -> RequestBuilder site ()
- setMethod :: Method -> RequestBuilder site ()
- addPostParam :: Text -> Text -> RequestBuilder site ()
- addGetParam :: Text -> Text -> RequestBuilder site ()
- addFile :: Text -> FilePath -> Text -> RequestBuilder site ()
- setRequestBody :: Yesod site => ByteString -> RequestBuilder site ()
- type RequestBuilder site = StateT (RequestBuilderData site) IO
- setUrl :: (Yesod site, RedirectUrl site url) => url -> RequestBuilder site ()
- byLabel :: Text -> Text -> RequestBuilder site ()
- fileByLabel :: Text -> FilePath -> Text -> RequestBuilder site ()
- addToken :: RequestBuilder site ()
- addToken_ :: Query -> RequestBuilder site ()
- addNonce :: RequestBuilder site ()
- addNonce_ :: Query -> RequestBuilder site ()
- addTokenFromCookie :: RequestBuilder site ()
- addTokenFromCookieNamedToHeaderNamed :: ByteString -> CI ByteString -> RequestBuilder site ()
- assertEqual :: Eq a => String -> a -> a -> YesodExample site ()
- assertHeader :: CI ByteString -> ByteString -> YesodExample site ()
- assertNoHeader :: CI ByteString -> YesodExample site ()
- statusIs :: Int -> YesodExample site ()
- bodyEquals :: String -> YesodExample site ()
- bodyContains :: String -> YesodExample site ()
- htmlAllContain :: Query -> String -> YesodExample site ()
- htmlAnyContain :: Query -> String -> YesodExample site ()
- htmlNoneContain :: Query -> String -> YesodExample site ()
- htmlCount :: Query -> Int -> YesodExample site ()
- getTestYesod :: YesodExample site site
- getResponse :: YesodExample site (Maybe SResponse)
- getRequestCookies :: RequestBuilder site Cookies
- printBody :: YesodExample site ()
- printMatches :: Query -> YesodExample site ()
- htmlQuery :: Query -> YesodExample site [HtmlLBS]
- parseHTML :: HtmlLBS -> Cursor
- withResponse :: (SResponse -> YesodExample site a) -> YesodExample site a
Declaring and running your test suite
yesodSpec :: YesodDispatch site => site -> YesodSpec site -> Spec Source
type YesodSpec site = Writer [YesodSpecTree site] () Source
Corresponds to hspec's Spec
.
Since 1.2.0
yesodSpecWithSiteGenerator :: YesodDispatch site => IO site -> YesodSpec site -> Spec Source
Same as yesodSpec, but instead of taking already built site it takes an action which produces site for each test.
yesodSpecApp :: YesodDispatch site => site -> IO Application -> YesodSpec site -> Spec Source
Same as yesodSpec, but instead of taking a site it
takes an action which produces the Application
for each test.
This lets you use your middleware from makeApplication
type YesodExample site = StateT (YesodExampleData site) IO Source
A single test case, to be run with yit
.
Since 1.2.0
data YesodExampleData site Source
The state used in a single test case defined using yit
Since 1.2.4
YesodExampleData | |
|
YesodDispatch site => Example (StateT (YesodExampleData site) IO a) | |
type Arg (StateT (YesodExampleData site) IO a) = site |
data YesodSpecTree site Source
Internal data structure, corresponding to hspec's YesodSpecTree
.
Since 1.2.0
YesodSpecGroup String [YesodSpecTree site] | |
YesodSpecItem String (YesodExample site ()) |
ydescribe :: String -> YesodSpec site -> YesodSpec site Source
Start describing a Tests suite keeping cookies and a reference to the tested Application
and ConnectionPool
yit :: String -> YesodExample site () -> YesodSpec site Source
Describe a single test that keeps cookies, and a reference to the last response.
Making requests
You can construct requests with the RequestBuilder
monad, which lets you
set the URL and add parameters, headers, and files. Helper functions are provided to
lookup fields by label and to add the current CSRF token from your forms.
Once built, the request can be executed with the request
method.
Convenience functions like get
and post
build and execute common requests.
get :: (Yesod site, RedirectUrl site url) => url -> YesodExample site () Source
Perform a GET request to url
.
Examples
get HomeR
get ("http://google.com" :: Text)
post :: (Yesod site, RedirectUrl site url) => url -> YesodExample site () Source
Perform a POST request to url
.
Examples
post HomeR
postBody :: (Yesod site, RedirectUrl site url) => url -> ByteString -> YesodExample site () Source
Perform a POST request to url
with the given body.
Examples
postBody HomeR "foobar"
import Data.Aeson postBody HomeR (encode $ object ["age" .= (1 :: Integer)])
request :: Yesod site => RequestBuilder site () -> YesodExample site () Source
The general interface for performing requests. request
takes a RequestBuilder
,
constructs a request, and executes it.
The RequestBuilder
allows you to build up attributes of the request, like the
headers, parameters, and URL of the request.
Examples
request $ do addToken byLabel "First Name" "Felipe" setMethod "PUT" setUrl NameR
addRequestHeader :: Header -> RequestBuilder site () Source
Adds the given header to the request; see Network.HTTP.Types.Header for creating Header
s.
Examples
import Network.HTTP.Types.Header request $ do addRequestHeader (hUserAgent, "Chrome/41.0.2228.0")
setMethod :: Method -> RequestBuilder site () Source
Sets the HTTP method used by the request.
Examples
request $ do setMethod "POST"
import Network.HTTP.Types.Method request $ do setMethod methodPut
addPostParam :: Text -> Text -> RequestBuilder site () Source
Add a parameter with the given name and value to the request body.
addGetParam :: Text -> Text -> RequestBuilder site () Source
Add a parameter with the given name and value to the query string.
:: Text | The parameter name for the file. |
-> FilePath | The path to the file. |
-> Text | The MIME type of the file, e.g. "image/png". |
-> RequestBuilder site () |
Add a file to be posted with the current request.
Adding a file will automatically change your request content-type to be multipart/form-data.
Examples
request $ do addFile "profile_picture" "static/img/picture.png" "img/png"
setRequestBody :: Yesod site => ByteString -> RequestBuilder site () Source
Simple way to set HTTP request body
Examples
request $ do setRequestBody "foobar"
import Data.Aeson request $ do setRequestBody $ encode $ object ["age" .= (1 :: Integer)]
type RequestBuilder site = StateT (RequestBuilderData site) IO Source
The RequestBuilder
state monad constructs a URL encoded string of arguments
to send with your requests. Some of the functions that run on it use the current
response to analyze the forms that the server is expecting to receive.
setUrl :: (Yesod site, RedirectUrl site url) => url -> RequestBuilder site () Source
Sets the URL used by the request.
Examples
request $ do setUrl HomeR
request $ do setUrl ("http://google.com/" :: Text)
Adding fields by label
Yesod can auto generate field names, so you are never sure what the argument name should be for each one of your inputs when constructing your requests. What you do know is the label of the field. These functions let you add parameters to your request based on currently displayed label names.
:: Text | The text contained in the |
-> Text | The value to set the parameter to. |
-> RequestBuilder site () |
Finds the <label>
with the given value, finds its corresponding <input>
, then adds a parameter
for that input to the request body.
Examples
Given this HTML, we want to submit f1=Michael
to the server:
<form method="POST"> <label for="user">Username</label> <input id="user" name="f1" /> </form>
You can set this parameter like so:
request $ do byLabel "Username" "Michael"
This function also supports the implicit label syntax, in which
the <input>
is nested inside the <label>
rather than specified with for
:
<form method="POST"> <label>Username <input name="f1"> </label> </form>
:: Text | The text contained in the |
-> FilePath | The path to the file. |
-> Text | The MIME type of the file, e.g. "image/png". |
-> RequestBuilder site () |
Finds the <label>
with the given value, finds its corresponding <input>
, then adds a file for that input to the request body.
Examples
Given this HTML, we want to submit a file with the parameter name f1
to the server:
<form method="POST"> <label for="imageInput">Please submit an image</label> <input id="imageInput" type="file" name="f1" accept="image/*"> </form>
You can set this parameter like so:
request $ do fileByLabel "Please submit an image" "static/img/picture.png" "img/png"
This function also supports the implicit label syntax, in which
the <input>
is nested inside the <label>
rather than specified with for
:
<form method="POST"> <label>Please submit an image <input type="file" name="f1"> </label> </form>
CSRF Tokens
In order to prevent CSRF exploits, yesod-form adds a hidden input to your forms with the name "_token". This token is a randomly generated, per-session value.
In order to prevent your forms from being rejected in tests, use one of these functions to add the token to your request.
addToken :: RequestBuilder site () Source
For responses that display a single form, just lookup the only CSRF token available.
Examples
request $ do addToken
addToken_ :: Query -> RequestBuilder site () Source
Lookups the hidden input named "_token" and adds its value to the params. Receives a CSS selector that should resolve to the form element containing the token.
Examples
request $ do addToken_ "#formID"
addNonce :: RequestBuilder site () Source
Deprecated: Use addToken
instead; addNonce
will be removed in the next major version. Reasoning: Yesod's CSRF tokens are not actually nonces (one-time values), so yesod-form moved to calling them tokens instead. yesod-test is now using the word token as well. See https://github.com/yesodweb/yesod/issues/914 for details.
An alias for addToken
.
addNonce_ :: Query -> RequestBuilder site () Source
Deprecated: Use addToken_
instead; addNonce_
will be removed in the next major version. Reasoning: Yesod's CSRF tokens are not actually nonces (one-time values), so yesod-form moved to calling them tokens instead. yesod-test is now using the word token as well. See https://github.com/yesodweb/yesod/issues/914 for details.
An alias for addToken_
.
addTokenFromCookie :: RequestBuilder site () Source
Calls addTokenFromCookieNamedToHeaderNamed
with the defaultCsrfCookieName
and defaultCsrfHeaderName
.
Use this function if you're using the CSRF middleware from Yesod.Core and haven't customized the cookie or header name.
Examples
request $ do addTokenFromCookie
Since 1.4.3.2
addTokenFromCookieNamedToHeaderNamed Source
:: ByteString | The name of the cookie |
-> CI ByteString | The name of the header |
-> RequestBuilder site () |
Looks up the CSRF token stored in the cookie with the given name and adds it to the request headers. An error is thrown if the cookie can't be found.
Use this function if you're using the CSRF middleware from Yesod.Core and have customized the cookie or header name.
See Yesod.Core.Handler for details on this approach to CSRF protection.
Examples
import Data.CaseInsensitive (CI) request $ do addTokenFromCookieNamedToHeaderNamed "cookieName" (CI "headerName")
Since 1.4.3.2
Assertions
assertEqual :: Eq a => String -> a -> a -> YesodExample site () Source
Asserts that the two given values are equal.
assertHeader :: CI ByteString -> ByteString -> YesodExample site () Source
Assert the given header key/value pair was returned.
assertNoHeader :: CI ByteString -> YesodExample site () Source
Assert the given header was not included in the response.
statusIs :: Int -> YesodExample site () Source
Assert the last response status is as expected.
bodyEquals :: String -> YesodExample site () Source
Assert the last response is exactly equal to the given text. This is useful for testing API responses.
bodyContains :: String -> YesodExample site () Source
Assert the last response has the given text. The check is performed using the response body in full text form.
htmlAllContain :: Query -> String -> YesodExample site () Source
Queries the HTML using a CSS selector, and all matched elements must contain the given string.
htmlAnyContain :: Query -> String -> YesodExample site () Source
Queries the HTML using a CSS selector, and passes if any matched element contains the given string.
Since 0.3.5
htmlNoneContain :: Query -> String -> YesodExample site () Source
Queries the HTML using a CSS selector, and fails if any matched element contains the given string (in other words, it is the logical inverse of htmlAnyContains).
Since 1.2.2
htmlCount :: Query -> Int -> YesodExample site () Source
Performs a CSS query on the last response and asserts the matched elements are as many as expected.
Grab information
getTestYesod :: YesodExample site site Source
Get the foundation value used for the current test.
Since 1.2.0
getResponse :: YesodExample site (Maybe SResponse) Source
Get the most recently provided response value, if available.
Since 1.2.0
getRequestCookies :: RequestBuilder site Cookies Source
Returns the Cookies
from the most recent request. If a request hasn't been made, an error is raised.
Examples
request $ do cookies <- getRequestCookies liftIO $ putStrLn $ "Cookies are: " ++ show cookies
Since 1.4.3.2
Debug output
printBody :: YesodExample site () Source
Outputs the last response body to stderr (So it doesn't get captured by HSpec)
printMatches :: Query -> YesodExample site () Source
Performs a CSS query and print the matches to stderr.
Utils for building your own assertions
Please consider generalizing and contributing the assertions you write.
htmlQuery :: Query -> YesodExample site [HtmlLBS] Source
Query the last response using CSS selectors, returns a list of matched fragments
parseHTML :: HtmlLBS -> Cursor Source
Use HXT to parse a value from an HTML tag. Check for usage examples in this module's source.
withResponse :: (SResponse -> YesodExample site a) -> YesodExample site a Source
Performs a given action using the last response. Use this to create response-level assertions