yesod-test 1.6.9 → 1.6.9.1
raw patch · 3 files changed
+196/−10 lines, 3 filesdep ~basePVP ok
version bump matches the API change (PVP)
Dependency ranges changed: base
API changes (from Hackage documentation)
Files
- ChangeLog.md +5/−0
- Yesod/Test.hs +186/−7
- yesod-test.cabal +5/−3
ChangeLog.md view
@@ -1,5 +1,10 @@ # ChangeLog for yesod-test +## 1.6.9.1++* Improve documentation [#1676](https://github.com/yesodweb/yesod/pull/1676)+* Require GHC 8.2 (base >= 4.10)+ ## 1.6.9 Add `requireJSONResponse` function [#1646](https://github.com/yesodweb/yesod/pull/1646)
Yesod/Test.hs view
@@ -10,7 +10,7 @@ {-| Yesod.Test is a pragmatic framework for testing web applications built-using wai and persistent.+using wai. 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/.@@ -24,9 +24,87 @@ 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+=== Example project++The best way to see an example project using yesod-test is to create a scaffolded Yesod project:++@stack new projectname yesod-sqlite@++(See https://github.com/commercialhaskell/stack-templates/wiki#yesod for the full list of Yesod templates)++The scaffolded project makes your database directly available in tests, so you can use 'runDB' to set up backend pre-conditions, or to assert that your session is having the desired effect.+It also handles wiping your database between each test. +=== Example code++The code below should give you a high-level idea of yesod-test's capabilities.+Note that it uses helper functions like @withApp@ and @runDB@ from the scaffolded project; these aren't provided by yesod-test.++@+spec :: Spec+spec = withApp $ do+ describe \"Homepage\" $ do+ it "loads the homepage with a valid status code" $ do+ 'get' HomeR+ 'statusIs' 200+ describe \"Login Form\" $ do+ it "Only allows dashboard access after logging in" $ do+ 'get' DashboardR+ 'statusIs' 401++ 'get' HomeR+ -- Assert a \<p\> tag exists on the page+ 'htmlAnyContain' \"p\" \"Login\"++ -- yesod-test provides a 'RequestBuilder' monad for building up HTTP requests+ 'request' $ do+ -- Lookup the HTML \<label\> with the text Username, and set a POST parameter for that field with the value Felipe+ 'byLabelExact' \"Username\" \"Felipe\"+ 'byLabelExact' \"Password\" "pass\"+ 'setMethod' \"POST\"+ 'setUrl' SignupR+ 'statusIs' 200++ -- The previous request will have stored a session cookie, so we can access the dashboard now+ 'get' DashboardR+ 'statusIs' 200++ -- Assert a user with the name Felipe was added to the database+ [Entity userId user] <- runDB $ selectList [] []+ 'assertEq' "A single user named Felipe is created" (userUsername user) \"Felipe\"+ describe \"JSON\" $ do+ it "Can make requests using JSON, and parse JSON responses" $ do+ -- Precondition: Create a user with the name \"George\"+ runDB $ insert_ $ User \"George\" "pass"++ 'request' $ do+ -- Use the Aeson library to send JSON to the server+ 'setRequestBody' ('Data.Aeson.encode' $ LoginRequest \"George\" "pass")+ 'addRequestHeader' (\"Accept\", "application/json")+ 'addRequestHeader' ("Content-Type", "application/json")+ 'setUrl' LoginR+ 'statusIs' 200++ -- Parse the request's response as JSON+ (signupResponse :: SignupResponse) <- 'requireJSONResponse'+@++=== HUnit / HSpec integration++yesod-test is built on top of hspec, which is itself built on top of HUnit.+You can use existing assertion functions from those libraries, but you'll need to use `liftIO` with them:++@+liftIO $ actualTimesCalled `'Test.Hspec.Expectations.shouldBe'` expectedTimesCalled -- hspec assertion+@++@+liftIO $ 'Test.HUnit.Base.assertBool' "a is greater than b" (a > b) -- HUnit assertion+@++yesod-test provides a handful of assertion functions that are already lifted, such as 'assertEq', as well.+ -} module Yesod.Test@@ -459,7 +537,7 @@ -- | Asserts that the two given values are equal. ----- In case they are not equal, error message includes the two values.+-- In case they are not equal, the error message includes the two values. -- -- @since 1.5.2 assertEq :: (HasCallStack, Eq a, Show a) => String -> a -> a -> YesodExample site ()@@ -471,7 +549,7 @@ -- | Asserts that the two given values are not equal. ----- In case they are equal, error mesasge includes the values.+-- In case they are equal, the error message includes the values. -- -- @since 1.5.6 assertNotEq :: (HasCallStack, Eq a, Show a) => String -> a -> a -> YesodExample site ()@@ -491,6 +569,11 @@ assertEqualNoShow msg a b = liftIO $ HUnit.assertBool msg (a == b) -- | Assert the last response status is as expected.+--+-- ==== __Examples__+--+-- > get HomeR+-- > statusIs 200 statusIs :: HasCallStack => Int -> YesodExample site () statusIs number = withResponse $ \ SResponse { simpleStatus = s } -> liftIO $ flip HUnit.assertBool (H.statusCode s == number) $ concat@@ -499,6 +582,17 @@ ] -- | Assert the given header key/value pair was returned.+--+-- ==== __Examples__+--+-- > {-# LANGUAGE OverloadedStrings #-}+-- > get HomeR+-- > assertHeader "key" "value"+--+-- > import qualified Data.CaseInsensitive as CI+-- > import qualified Data.ByteString.Char8 as BS8+-- > getHomeR+-- > assertHeader (CI.mk (BS8.pack "key")) (BS8.pack "value") assertHeader :: HasCallStack => CI BS8.ByteString -> BS8.ByteString -> YesodExample site () assertHeader header value = withResponse $ \ SResponse { simpleHeaders = h } -> case lookup header h of@@ -519,6 +613,17 @@ ] -- | Assert the given header was not included in the response.+--+-- ==== __Examples__+--+-- > {-# LANGUAGE OverloadedStrings #-}+-- > get HomeR+-- > assertNoHeader "key"+--+-- > import qualified Data.CaseInsensitive as CI+-- > import qualified Data.ByteString.Char8 as BS8+-- > getHomeR+-- > assertNoHeader (CI.mk (BS8.pack "key")) assertNoHeader :: HasCallStack => CI BS8.ByteString -> YesodExample site () assertNoHeader header = withResponse $ \ SResponse { simpleHeaders = h } -> case lookup header h of@@ -532,6 +637,11 @@ -- | Assert the last response is exactly equal to the given text. This is -- useful for testing API responses.+--+-- ==== __Examples__+--+-- > get HomeR+-- > bodyEquals "<html><body><h1>Hello, World</h1></body></html>" bodyEquals :: HasCallStack => String -> YesodExample site () bodyEquals text = withResponse $ \ res -> do let actual = simpleBody res@@ -544,6 +654,11 @@ -- | Assert the last response has the given text. The check is performed using the response -- body in full text form.+--+-- ==== __Examples__+--+-- > get HomeR+-- > bodyContains "<h1>Foo</h1>" bodyContains :: HasCallStack => String -> YesodExample site () bodyContains text = withResponse $ \ res -> liftIO $ HUnit.assertBool ("Expected body to contain " ++ text) $@@ -551,6 +666,12 @@ -- | Assert the last response doesn't have the given text. The check is performed using the response -- body in full text form.+--+-- ==== __Examples__+--+-- > get HomeR+-- > bodyNotContains "<h1>Foo</h1>+-- -- @since 1.5.3 bodyNotContains :: HasCallStack => String -> YesodExample site () bodyNotContains text = withResponse $ \ res ->@@ -562,6 +683,16 @@ -- | Queries the HTML using a CSS selector, and all matched elements must contain -- the given string.+--+-- ==== __Examples__+--+-- > {-# LANGUAGE OverloadedStrings #-}+-- > get HomeR+-- > htmlAllContain "p" "Hello" -- Every <p> tag contains the string "Hello"+--+-- > import qualified Data.Text as T+-- > get HomeR+-- > htmlAllContain (T.pack "h1#mainTitle") "Sign Up Now!" -- All <h1> tags with the ID mainTitle contain the string "Sign Up Now!" htmlAllContain :: HasCallStack => Query -> String -> YesodExample site () htmlAllContain query search = do matches <- htmlQuery query@@ -573,6 +704,12 @@ -- | Queries the HTML using a CSS selector, and passes if any matched -- element contains the given string. --+-- ==== __Examples__+--+-- > {-# LANGUAGE OverloadedStrings #-}+-- > get HomeR+-- > htmlAnyContain "p" "Hello" -- At least one <p> tag contains the string "Hello"+-- -- Since 0.3.5 htmlAnyContain :: HasCallStack => Query -> String -> YesodExample site () htmlAnyContain query search = do@@ -584,8 +721,14 @@ -- | 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).+-- inverse of htmlAnyContain). --+-- ==== __Examples__+--+-- > {-# LANGUAGE OverloadedStrings #-}+-- > get HomeR+-- > htmlNoneContain ".my-class" "Hello" -- No tags with the class "my-class" contain the string "Hello"+-- -- Since 1.2.2 htmlNoneContain :: HasCallStack => Query -> String -> YesodExample site () htmlNoneContain query search = do@@ -597,6 +740,12 @@ -- | Performs a CSS query on the last response and asserts the matched elements -- are as many as expected.+--+-- ==== __Examples__+--+-- > {-# LANGUAGE OverloadedStrings #-}+-- > get HomeR+-- > htmlCount "p" 3 -- There are exactly 3 <p> tags in the response htmlCount :: HasCallStack => Query -> Int -> YesodExample site () htmlCount query count = do matches <- fmap DL.length $ htmlQuery query@@ -634,18 +783,42 @@ failure $ T.concat ["Failed to parse JSON response; error: ", T.pack err, "JSON: ", bodyPreview] Right v -> return v --- | Outputs the last response body to stderr (So it doesn't get captured by HSpec)+-- | Outputs the last response body to stderr (So it doesn't get captured by HSpec). Useful for debugging.+--+-- ==== __Examples__+--+-- > get HomeR+-- > printBody printBody :: YesodExample site () printBody = withResponse $ \ SResponse { simpleBody = b } -> liftIO $ BSL8.hPutStrLn stderr b -- | Performs a CSS query and print the matches to stderr.+--+-- ==== __Examples__+--+-- > {-# LANGUAGE OverloadedStrings #-}+-- > get HomeR+-- > printMatches "h1" -- Prints all h1 tags printMatches :: Query -> YesodExample site () printMatches query = do matches <- htmlQuery query liftIO $ hPutStrLn stderr $ show matches --- | Add a parameter with the given name and value to the request body.+-- | Add a parameter with the given name and value to the request body. +-- This function can be called multiple times to add multiple parameters, and be mixed with calls to 'addFile'.+--+-- "Post parameter" is an informal description of what is submitted by making an HTTP POST with an HTML @\<form\>@.+-- Like HTML @\<form\>@s, yesod-test will default to a @Content-Type@ of @application/x-www-form-urlencoded@ if no files are added,+-- and switch to @multipart/form-data@ if files are added.+--+-- Calling this function after using 'setRequestBody' will raise an error.+--+-- ==== __Examples__+--+-- > {-# LANGUAGE OverloadedStrings #-}+-- > post $ do+-- > addPostParam "key" "value" addPostParam :: T.Text -> T.Text -> RequestBuilder site () addPostParam name value = modifySIO $ \rbd -> rbd { rbdPostData = (addPostData (rbdPostData rbd)) }@@ -654,6 +827,12 @@ MultipleItemsPostData $ ReqKvPart name value : posts -- | Add a parameter with the given name and value to the query string.+--+-- ==== __Examples__+--+-- > {-# LANGUAGE OverloadedStrings #-}+-- > request $ do+-- > addGetParam "key" "value" -- Adds ?key=value to the URL addGetParam :: T.Text -> T.Text -> RequestBuilder site () addGetParam name value = modifySIO $ \rbd -> rbd { rbdGets = (TE.encodeUtf8 name, Just $ TE.encodeUtf8 value)
yesod-test.cabal view
@@ -1,5 +1,5 @@ name: yesod-test-version: 1.6.9+version: 1.6.9.1 license: MIT license-file: LICENSE author: Nubis <nubis@woobiz.com.ar>@@ -7,17 +7,18 @@ synopsis: integration testing for WAI/Yesod Applications category: Web, Yesod, Testing stability: Experimental-cabal-version: >= 1.8+cabal-version: >= 1.10 build-type: Simple homepage: http://www.yesodweb.com description: API docs and the README are available at <http://www.stackage.org/package/yesod-test> extra-source-files: README.md, LICENSE, test/main.hs, ChangeLog.md library+ default-language: Haskell2010 build-depends: HUnit >= 1.2 , aeson , attoparsec >= 0.10- , base >= 4.3 && < 5+ , base >= 4.10 && < 5 , blaze-builder , blaze-html >= 0.5 , bytestring >= 0.9@@ -47,6 +48,7 @@ ghc-options: -Wall test-suite test+ default-language: Haskell2010 type: exitcode-stdio-1.0 main-is: main.hs hs-source-dirs: test