packages feed

hspec-wai 0.1.0 → 0.2.0

raw patch · 6 files changed

+188/−43 lines, 6 filesdep +aesondep +aeson-qqdep +doctestPVP ok

version bump matches the API change (PVP)

Dependencies added: aeson, aeson-qq, doctest, scotty, template-haskell

API changes (from Hackage documentation)

- Test.Hspec.Wai.Internal: data WaiSession a
+ Test.Hspec.Wai: data WaiSession a
+ Test.Hspec.Wai: liftIO :: MonadIO m => forall a. IO a -> m a
+ Test.Hspec.Wai: type WaiExpectation = WaiSession ()
+ Test.Hspec.Wai.Internal: WaiSession :: Session a -> WaiSession a
+ Test.Hspec.Wai.Internal: newtype WaiSession a
+ Test.Hspec.Wai.Internal: unWaiSession :: WaiSession a -> Session a
+ Test.Hspec.Wai.JSON: class FromValue a
+ Test.Hspec.Wai.JSON: fromValue :: FromValue a => Value -> a
+ Test.Hspec.Wai.JSON: instance FromValue ByteString
+ Test.Hspec.Wai.JSON: instance FromValue ResponseMatcher
+ Test.Hspec.Wai.JSON: json :: QuasiQuoter

Files

README.lhs view
@@ -7,35 +7,48 @@ ## Example  ~~~ {.haskell}-{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE OverloadedStrings, QuasiQuotes #-} module Main (main) where  import           Test.Hspec import           Test.Hspec.Wai+import           Test.Hspec.Wai.JSON -import           Network.HTTP.Types (status200, hContentType)-import           Network.Wai (Application, responseLBS)+import           Network.HTTP.Types (hContentType)+import           Network.Wai (Application) +import           Data.Aeson (Value(..), object, (.=))+import qualified Web.Scotty as S+ main :: IO () main = hspec spec -app :: Application-app _ = ($ responseLBS status200 [("Content-Type", "text/plain")] "hello")+app :: IO Application+app = S.scottyApp $ do+  S.get "/" $ do+    S.text "hello" +  S.get "/some-json" $ do+    S.json $ object ["foo" .= Number 23, "bar" .= Number 42]+ spec :: Spec-spec = before (return app) $ do-  describe "GET /foo" $ do+spec = with app $ do+  describe "GET /" $ do     it "reponds with 200" $ do-      get "/foo" `shouldRespondWith` 200+      get "/" `shouldRespondWith` 200      it "reponds with 'hello'" $ do-      get "/foo" `shouldRespondWith` "hello"+      get "/" `shouldRespondWith` "hello"      it "reponds with 200 / 'hello'" $ do-      get "/foo" `shouldRespondWith` "hello" {matchStatus = 200}+      get "/" `shouldRespondWith` "hello" {matchStatus = 200}      it "has Content-Type: text/plain" $ do-      get "/foo" `shouldRespondWith` 200 {matchHeaders = [(hContentType, "text/plain")]}+      get "/" `shouldRespondWith` 200 {matchHeaders = [(hContentType, "text/plain")]}++  describe "GET /some-json" $ do+    it "reponds with some JSON" $ do+      get "/some-json" `shouldRespondWith` [json|{foo: 23, bar: 42}|] ~~~  ## Contributing
hspec-wai.cabal view
@@ -1,5 +1,5 @@ name:             hspec-wai-version:          0.1.0+version:          0.2.0 license:          MIT license-file:     LICENSE copyright:        (c) 2012-2014 Fujimura Daisuke,@@ -24,6 +24,7 @@   exposed-modules:       Test.Hspec.Wai       Test.Hspec.Wai.Internal+      Test.Hspec.Wai.JSON   other-modules:       Test.Hspec.Wai.Util       Test.Hspec.Wai.Matcher@@ -37,6 +38,9 @@     , wai >= 3     , wai-extra >= 3     , hspec2+    , template-haskell+    , aeson+    , aeson-qq >= 0.7.1  test-suite spec   type:@@ -60,10 +64,23 @@      , hspec-meta -test-suite example+test-suite doctests+  main-is:+      doctests.hs   type:       exitcode-stdio-1.0   ghc-options:+      -Wall -Werror -threaded+  hs-source-dirs:+      test+  build-depends:+      base    == 4.*+    , doctest >= 0.9.4.1++test-suite README+  type:+      exitcode-stdio-1.0+  ghc-options:       -Wall -Werror -pgmL markdown-unlit   main-is:       README.lhs@@ -73,4 +90,6 @@     , hspec2     , hspec-wai     , http-types+    , aeson     , wai+    , scotty
src/Test/Hspec/Wai.hs view
@@ -1,12 +1,24 @@-{-# LANGUAGE OverloadedStrings, GeneralizedNewtypeDeriving, FlexibleInstances, TypeFamilies #-}+{-# LANGUAGE OverloadedStrings #-}+-- | Have a look at the <https://github.com/hspec/hspec-wai#readme README> for+-- an example of how to use this library. module Test.Hspec.Wai (-  with+-- * Types+  WaiSession+, WaiExpectation++-- * Performing requests , get , post , put , request++-- * Matching on the response , shouldRespondWith , ResponseMatcher(..)++-- * Helpers and re-exports+, liftIO+, with ) where  import           Data.Foldable@@ -22,46 +34,64 @@ import           Test.Hspec.Wai.Internal import           Test.Hspec.Wai.Matcher +-- | An alias for `before`. with :: IO a -> SpecWith a -> Spec with = before --- |--- Passs if+-- | Set the expectation that a response matches a specified `ResponseMatcher`. -----   * the given number matches with the HTTP Status code of the response.---   * the given string matches with the body of the response.---   * the given `ResponseMatcher` matches with the response.+-- A @ResponseMatcher@ matches a response if: ----- Example:+-- * the specified status matches the HTTP response status code ----- > get "/foo" `shouldRespondWith` 200                         -- Pass--- > get "/foo" `shouldRespondWith` "bar"                       -- Pass if the body is "bar"--- > get "/foo" `shouldRespondWith` "bar" { matchStatus = 200 } -- Pass if the body is "bar" and status is 200+-- * the specified body (if any) matches the response body ---+-- * the response has all of the specified `Header` fields+--   (the response may have arbitrary additional `Header` fields)+--+-- You can use @ResponseMatcher@'s (broken) `Num` instance to match for a HTTP+-- status code:+--+-- > get "/" `shouldRespondWith` 200+-- > -- matches if status is 200+--+-- You can use @ResponseMatcher@'s `IsString` instance to match for a HTTP+-- status @200@ and a body:+--+-- > get "/" `shouldRespondWith` "foo"+-- > -- matches if body is "foo" and status is 200+--+-- If you want to match for a different HTTP status, you can use record update+-- notation to specify `matchStatus` explicitly:+--+-- > get "/" `shouldRespondWith` "foo" {matchStatus = 404}+-- > -- matches if body is "foo" and status is 404+--+-- If you want to require a specific header field you can specify+-- `matchHeaders`:+--+-- > get "/" `shouldRespondWith` "foo" {matchHeaders = "Content-Type: text/plain"}+-- > -- matches if body is "foo", status is 200 and ther is a header field "Content-Type: text/plain" shouldRespondWith :: WaiSession SResponse -> ResponseMatcher -> WaiExpectation shouldRespondWith action matcher = do   r <- action   forM_ (match r matcher) (liftIO . expectationFailure) --- |--- | Performs `GET` request to running app.+-- | Perform a @GET@ request to the application under test. get :: ByteString -> WaiSession SResponse-get p = request methodGet p ""+get path = request methodGet path "" --- |--- | Performs `POST` request to running app.+-- | Perform a @POST@ request to the application under test. post :: ByteString -> LB.ByteString -> WaiSession SResponse post = request methodPost --- |--- | Performs `PUT` request to running app.+-- | Perform a @PUT@ request to the application under test. put :: ByteString -> LB.ByteString -> WaiSession SResponse put = request methodPut --- |--- | Performs request to running app, with HTTP Method, path and body.+-- | Perform a request to the application under test, with specified HTTP+-- method, request path and body. request :: Method -> ByteString -> LB.ByteString -> WaiSession SResponse-request m p b = getApp >>= liftIO . runSession (Wai.srequest $ SRequest req b)+request method path body = getApp >>= liftIO . runSession (Wai.srequest $ SRequest req body)   where-    req = setPath defaultRequest {requestMethod = m} p+    req = setPath defaultRequest {requestMethod = method} path
src/Test/Hspec/Wai/Internal.hs view
@@ -1,10 +1,10 @@-{-# LANGUAGE FlexibleInstances          #-}+{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE OverloadedStrings          #-}-{-# LANGUAGE TypeFamilies               #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TypeFamilies #-} module Test.Hspec.Wai.Internal (   WaiExpectation-, WaiSession+, WaiSession(..) , runWaiSession , getApp ) where@@ -12,13 +12,18 @@ import           Control.Applicative import           Control.Monad.IO.Class import           Control.Monad.Trans.Reader-import           Network.Wai                (Application)-import           Network.Wai.Test           hiding (request)+import           Network.Wai (Application)+import           Network.Wai.Test hiding (request) import           Test.Hspec-import           Test.Hspec.Core            (Example (..))+import           Test.Hspec.Core (Example (..)) +-- | An expectation in the `WaiSession` monad.  Failing expectations are+-- communicated through exceptions (similar to `Expectation` and+-- `Test.HUnit.Base.Assertion`). type WaiExpectation = WaiSession () +-- | A <http://www.yesodweb.com/book/web-application-interface WAI> test+-- session that carries the `Application` under test an some client state. newtype WaiSession a = WaiSession {unWaiSession :: Session a}   deriving (Functor, Applicative, Monad, MonadIO) 
+ src/Test/Hspec/Wai/JSON.hs view
@@ -0,0 +1,56 @@+{-# LANGUAGE OverloadedStrings, TemplateHaskell #-}+module Test.Hspec.Wai.JSON (+-- $setup+  json+, FromValue(..)+) where++import           Test.Hspec.Wai+import           Data.ByteString.Lazy (ByteString)+import           Data.Aeson (Value, encode)+import           Data.Aeson.QQ+import           Language.Haskell.TH.Quote++-- $setup+-- The examples in this module assume that you have the @QuasiQuotes@ language+-- extension enabled and that "Data.ByteString.Lazy.Char8" is imported+-- qualified as @L@:+--+-- >>> :set -XQuasiQuotes+-- >>> import Data.ByteString.Lazy.Char8 as L++-- | A `QuasiQuoter` for constructing JSON values.+--+-- The constructed value is polymorph and unifies to instances of `FromValue`.+--+-- When used as a `ResponseMatcher` it matches a response with+--+--  * a status code of @200@+--+--  * a @Content-Type@ header with value @application/json@+--+--  * the specified JSON as response body+--+-- When used as a @ByteString@ it creates a ByteString from the specified JSON+-- that can be used as a request body for e.g. @POST@ and @PUT@ requests.+--+-- Example:+--+-- >>> L.putStrLn [json|[23, {foo: 42}]|]+-- [23,{"foo":42}]+json :: QuasiQuoter+json = QuasiQuoter {+  quoteExp = \input -> [|fromValue $(quoteExp aesonQQ input)|]+, quotePat = const $ error "No quotePat defined for Test.Hspec.Wai.JSON.json"+, quoteType = const $ error "No quoteType defined for Test.Hspec.Wai.JSON.json"+, quoteDec = const $ error "No quoteDec defined for Test.Hspec.Wai.JSON.json"+}++class FromValue a where+  fromValue :: Value -> a++instance FromValue ResponseMatcher where+  fromValue v = ResponseMatcher 200 [("Content-Type", "application/json")] (Just . encode $ v)++instance FromValue ByteString where+  fromValue = encode
+ test/doctests.hs view
@@ -0,0 +1,22 @@+module Main where++import           Test.DocTest++main :: IO ()+main = doctest [+    "-isrc"+  , "-hide-all-packages"+  , "-package base"+  , "-package bytestring"+  , "-package text"+  , "-package transformers"+  , "-package case-insensitive"+  , "-package http-types"+  , "-package wai"+  , "-package wai-extra"+  , "-package hspec2"+  , "-package template-haskell"+  , "-package aeson"+  , "-package aeson-qq"+  , "src/Test/Hspec/Wai/JSON.hs"+  ]