HORIZON HASKELLDocslts/ghc-9.10.x248f8f02026-10-05Search names, modules, packages, or :: a typeCtrl K

GHC 9.10.3 · lts/ghc-9.10.x · 248f8f0 · 2026-10-05

Modulesnap-core-1.0.5.1Haskell2010

Snap.Test

The Snap.Test module contains primitives and combinators for testing Snap applications.

  • 5 types
  • 30 values

Combinators and types for testing Snap handlers.

0 declarations

Types

newtypenewtype RequestBuilder (m :: Type -> Type) a
#

RequestBuilder is a monad transformer that allows you to conveniently build a snap Request for testing.

Instances7MonadTrans, MonadState, Monad, Functor, MonadFail, Applicative, …

A request body of type "multipart/form-data" consists of a set of named form parameters, each of which can by either a list of regular form values or a set of file uploads.

Building Requests and testing handlers

valuebuildRequest :: MonadIO m => RequestBuilder m () -> m Request
#

Runs a RequestBuilder, producing the desired Request.

N.B. please don't use the request you get here in a real Snap application; things will probably break. Don't say you weren't warned :-)

Example:

ghci> :set -XOverloadedStrings
ghci> import qualified Data.Map as M
ghci> buildRequest $ get "/foo/bar" M.empty
GET /foo/bar HTTP/1.1
host: localhost

sn="localhost" c=127.0.0.1:60000 s=127.0.0.1:8080 ctx=/ clen=n/a
valuerunHandler
  1. :: MonadIO m
  2. => RequestBuilder m ()

    a request builder

  3. -> Snap a

    a web handler

  4. -> m Response
#

Given a web handler in the Snap monad, and a RequestBuilder defining a test request, runs the handler, producing an HTTP Response.

This function will produce almost exactly the same output as running the handler in a real server, except that chunked transfer encoding is not applied, and the "Transfer-Encoding" header is not set (this makes it easier to test response output).

Example:

ghci> :set -XOverloadedStrings
ghci> import qualified Data.Map as M
ghci> import Snap.Core
ghci> runHandler (get "foo/bar" M.empty) (Snap.Core.writeBS "Hello, world!")
HTTP/1.1 200 OK
server: Snap/test
date: Thu, 17 Jul 2014 21:03:23 GMT

Hello, world!
valueevalHandler :: MonadIO m => RequestBuilder m () -> Snap a -> m a
#

Given a web handler in the Snap monad, and a RequestBuilder defining a test request, runs the handler and returns the monadic value it produces.

Throws an exception if the Snap handler early-terminates with Snap.Core.finishWith or mzero.

Example:

ghci> :set -XOverloadedStrings
ghci> import Control.Monad
ghci> import qualified Data.Map as M
ghci> import Snap.Core
ghci> evalHandler (get "foo/bar" M.empty) (Snap.Core.writeBS "Hello, world!" >> return 42)
42
ghci> evalHandler (get "foo/bar" M.empty) mzero
*** Exception: No handler for request: failure was pass
valueevalHandlerM
  1. :: (MonadIO m, MonadSnap n)
  2. => (forall a. Request -> n a -> m a)

    a function defining how the MonadSnap monad should be run

  3. -> RequestBuilder m ()

    a request builder

  4. -> n b

    a web handler

  5. -> m b
#

Given a web handler in some arbitrary MonadSnap monad, a function specifying how to evaluate it within the context of the test monad, and a RequestBuilder defining a test request, runs the handler, returning the monadic value it produces.

Throws an exception if the Snap handler early-terminates with Snap.Core.finishWith or mzero.

Convenience functions for generating common types of HTTP requests

valueget
  1. :: MonadIO m
  2. => ByteString

    request path

  3. -> Params

    request's form parameters

  4. -> RequestBuilder m ()
#

Builds an HTTP "GET" request with the given query parameters.

Example:

ghci> :set -XOverloadedStrings
ghci> import qualified Data.Map as M
ghci> buildRequest $ get "/foo/bar" (M.fromList [("param0", ["baz", "quux"])])
GET /foo/bar?param0=baz&param0=quux HTTP/1.1
host: localhost

sn="localhost" c=127.0.0.1:60000 s=127.0.0.1:8080 ctx=/ clen=n/a
params: param0: ["baz","quux"]
valuehead :: HasCallStack => [a] -> a
#

This is a partial function, it throws an error on empty lists. Use pattern matching, uncons or listToMaybe instead. Consider refactoring to use Data.List.NonEmpty.

\mathcal{O}(1). Extract the first element of a list, which must be non-empty.

To disable the warning about partiality put {-# OPTIONS_GHC -Wno-x-partial -Wno-unrecognised-warning-flags #-} at the top of the file. To disable it throughout a package put the same options into ghc-options section of Cabal file. To disable it in GHCi put :set -Wno-x-partial -Wno-unrecognised-warning-flags into ~/.ghci config file. See also the migration guide.

Examples
Example1 expression
head [1, 2, 3]1
Example1 expression
head [1..]1
Example1 expression
head []*** Exception: Prelude.head: empty list
valuepostUrlEncoded
  1. :: MonadIO m
  2. => ByteString

    request path

  3. -> Params

    request's form parameters

  4. -> RequestBuilder m ()
#

Builds an HTTP "POST" request with the given form parameters, using the "application/x-www-form-urlencoded" MIME type.

Example:

ghci> :set -XOverloadedStrings
ghci> import qualified Data.Map as M
ghci> buildRequest $ postUrlEncoded "/foo/bar" (M.fromList [("param0", ["baz", "quux"])])
POST /foo/bar HTTP/1.1
content-type: application/x-www-form-urlencoded
content-length: 22
host: localhost

sn="localhost" c=127.0.0.1:60000 s=127.0.0.1:8080 ctx=/ clen=22
params: param0: ["baz","quux"]
valuepostMultipart
  1. :: MonadIO m
  2. => ByteString

    request path

  3. -> MultipartParams

    multipart form parameters

  4. -> RequestBuilder m ()
#

Builds an HTTP "POST" request with the given form parameters, using the "form-data/multipart" MIME type.

Example:

ghci> :set -XOverloadedStrings
ghci> buildRequest $ postMultipart "/foo/bar" [("param0", FormData ["baz", "quux"])]
POST /foo/bar HTTP/1.1
content-type: multipart/form-data; boundary=snap-boundary-572334111ec0c05ad4812481e8585dfa
content-length: 406
host: localhost

sn="localhost" c=127.0.0.1:60000 s=127.0.0.1:8080 ctx=/ clen=406
valueput
  1. :: MonadIO m
  2. => ByteString

    request path

  3. -> ByteString

    request body MIME content-type

  4. -> ByteString

    request body contents

  5. -> RequestBuilder m ()
#

Builds an HTTP "PUT" request.

Example:

ghci> :set -XOverloadedStrings
ghci> buildRequest $ put "/foo/bar" "text/plain" "some text"
PUT /foo/bar HTTP/1.1
content-type: text/plain
content-length: 9
host: localhost

sn="localhost" c=127.0.0.1:60000 s=127.0.0.1:8080 ctx=/ clen=9
valuepostRaw
  1. :: MonadIO m
  2. => ByteString

    request path

  3. -> ByteString

    request body MIME content-type

  4. -> ByteString

    request body contents

  5. -> RequestBuilder m ()
#

Builds a "raw" HTTP "POST" request, with the given MIME type and body contents.

Example:

ghci> :set -XOverloadedStrings
ghci> buildRequest $ postRaw "/foo/bar" "text/plain" "some text"
POST /foo/bar HTTP/1.1
content-type: text/plain
content-length: 9
host: localhost

sn="localhost" c=127.0.0.1:60000 s=127.0.0.1:8080 ctx=/ clen=9
valuedelete
  1. :: MonadIO m
  2. => ByteString

    request path

  3. -> Params

    request's form parameters

  4. -> RequestBuilder m ()
#

Builds an HTTP "DELETE" request with the given query parameters.

Example:

ghci> :set -XOverloadedStrings
ghci> import qualified Data.Map as M
ghci> buildRequest $ delete "/foo/bar" M.empty
DELETE /foo/bar HTTP/1.1
host: localhost

sn="localhost" c=127.0.0.1:60000 s=127.0.0.1:8080 ctx=/ clen=n/a

Precise control over building Requests

valueaddHeader :: Monad m => CI ByteString -> ByteString -> RequestBuilder m ()
#

Adds the given header to the request being built.

Example:

ghci> :set -XOverloadedStrings
ghci> import qualified Data.Map as M
ghci> :{
ghci| buildRequest $ do get "/foo/bar" M.empty
ghci|                   addHeader "Accept" "text/html"
ghci|                   addHeader "Accept" "text/plain"
ghci| :}
GET /foo/bar HTTP/1.1
accept: text/html,text/plain
host: localhost

sn="localhost" c=127.0.0.1:60000 s=127.0.0.1:8080 ctx=/ clen=n/a

Sets the request's content-type to the given MIME type.

Example:

ghci> :set -XOverloadedStrings
ghci> import qualified Data.Map as M
ghci> buildRequest $ put "/foo/bar" "text/html" "some text" >> setContentType "text/plain"
PUT /foo/bar HTTP/1.1
content-type: text/plain
content-length: 9
host: localhost

sn="localhost" c=127.0.0.1:60000 s=127.0.0.1:8080 ctx=/ clen=9
valuesetHeader :: Monad m => CI ByteString -> ByteString -> RequestBuilder m ()
#

Sets the given header in the request being built, overwriting any header with the same name already present.

Example:

ghci> :set -XOverloadedStrings
ghci> import qualified Data.Map as M
ghci> :{
ghci| buildRequest $ do get "/foo/bar" M.empty
ghci|                   setHeader "Accept" "text/html"
ghci|                   setHeader "Accept" "text/plain"
ghci| :}
GET /foo/bar HTTP/1.1
accept: text/plain
host: localhost

sn="localhost" c=127.0.0.1:60000 s=127.0.0.1:8080 ctx=/ clen=n/a
valueaddCookies :: Monad m => [Cookie] -> RequestBuilder m ()
#

Adds the given cookies to the request being built.

Example:

ghci> :set -XOverloadedStrings
ghci> import qualified Data.Map as M
ghci> import Snap.Core
ghci> let cookie = Cookie "name" "value" Nothing Nothing Nothing False False
ghci> buildRequest $ get "/foo/bar" M.empty >> addCookies [cookie]
GET /foo/bar HTTP/1.1
cookie: name=value
host: localhost

sn="localhost" c=127.0.0.1:60000 s=127.0.0.1:8080 ctx=/ clen=n/a
cookies: Cookie {cookieName = "name", cookieValue = "value", ...}
valuesetQueryString :: Monad m => Params -> RequestBuilder m ()
#

Escapes the given parameter mapping and sets it as the request's query string.

Example:

ghci> :set -XOverloadedStrings
ghci> import qualified Data.Map as M
ghci> buildRequest $ get "/foo/bar" M.empty >> setQueryString (M.fromList [("param0", ["baz"]), ("param1", ["qux"])])
GET /foo/bar?param0=baz&param1=qux HTTP/1.1
host: localhost

sn="localhost" c=127.0.0.1:60000 s=127.0.0.1:8080 ctx=/ clen=n/a
params: param0: ["baz"], param1: ["qux"]

Sets the request's query string to be the raw bytestring provided, without any escaping or other interpretation. Most users should instead choose the setQueryString function, which takes a parameter mapping.

Example:

ghci> :set -XOverloadedStrings
ghci> import qualified Data.Map as M
ghci> buildRequest $ get "/foo/bar" M.empty >> setQueryStringRaw "param0=baz&param1=qux"
GET /foo/bar?param0=baz&param1=qux HTTP/1.1
host: localhost

sn="localhost" c=127.0.0.1:60000 s=127.0.0.1:8080 ctx=/ clen=n/a
params: param0: ["baz"], param1: ["qux"]

Sets the request's path. The path provided must begin with a "/" and must not contain a query string; if you want to provide a query string in your test request, you must use setQueryString or setQueryStringRaw. Note that rqContextPath is never set by any RequestBuilder function.

Example:

ghci> :set -XOverloadedStrings
ghci> import qualified Data.Map as M
ghci> buildRequest $ get "/foo/bar" M.empty >> setRequestPath "/bar/foo"
GET /bar/foo HTTP/1.1
host: localhost

sn="localhost" c=127.0.0.1:60000 s=127.0.0.1:8080 ctx=/ clen=n/a
valuesetSecure :: Monad m => Bool -> RequestBuilder m ()
#

Controls whether the test request being generated appears to be an https request or not.

Example:

ghci> :set -XOverloadedStrings
ghci> import qualified Data.Map as M
ghci> buildRequest $ delete "/foo/bar" M.empty >> setSecure True
DELETE /foo/bar HTTP/1.1
host: localhost

sn="localhost" c=127.0.0.1:60000 s=127.0.0.1:8080 ctx=/ clen=n/a secure

HUnit Assertions

5 declarations

Given a Response, assert that its HTTP status code is 200 (success).

Example:

ghci> :set -XOverloadedStrings
ghci> import qualified Test.HUnit as T
ghci> let test = T.runTestTT . T.TestCase
ghci> test $ assertSuccess Snap.Core.emptyResponse
Cases: 1  Tried: 1  Errors: 0  Failures: 0
Counts {cases = 1, tried = 1, errors = 0, failures = 0}
ghci> test $ assertSuccess (Snap.Core.setResponseStatus 500 "Internal Server Error" Snap.Core.emptyResponse)
### Failure:
Expected success (200) but got (500)
expected: 200
 but got: 500
Cases: 1  Tried: 1  Errors: 0  Failures: 1
Counts {cases = 1, tried = 1, errors = 0, failures = 1}
valueassert404 :: Response -> Assertion
#

Given a Response, assert that its HTTP status code is 404 (Not Found).

Example:

ghci> :set -XOverloadedStrings
ghci> assert404 $ Snap.Core.setResponseStatus 404 "Not Found" Snap.Core.emptyResponse
ghci> assert404 Snap.Core.emptyResponse
*** Exception: HUnitFailure "Expected Not Found (404) but got (200)\nexpected: 404\n but got: 200"
valueassertRedirectTo
  1. :: ByteString

    The Response should redirect to this URI

  2. -> Response
  3. -> Assertion
#

Given a Response, assert that its HTTP status code is between 300 and 399 (a redirect), and that the Location header of the Response points to the specified URI.

Example:

ghci> :set -XOverloadedStrings
ghci> let r' = Snap.Core.setResponseStatus 301 "Moved Permanently" Snap.Core.emptyResponse
ghci> let r  = Snap.Core.setHeader "Location" "www.example.com" r'
ghci> assertRedirectTo "www.example.com" r
ghci> assertRedirectTo "www.example.com" Snap.Core.emptyResponse
*** Exception: HUnitFailure "Expected redirect but got status code (200)"

Given a Response, assert that its HTTP status code is between 300 and 399 (a redirect).

Example:

ghci> :set -XOverloadedStrings
ghci> assertRedirect $ Snap.Core.setResponseStatus 301 "Moved Permanently" Snap.Core.emptyResponse
ghci> assertRedirect Snap.Core.emptyResponse
*** Exception: HUnitFailure "Expected redirect but got status code (200)"
valueassertBodyContains
  1. :: ByteString

    Regexp that will match the body content

  2. -> Response
  3. -> Assertion
#

Given a Response, assert that its body matches the given regular expression.

Example:

ghci> :set -XOverloadedStrings
ghci> import qualified System.IO.Streams as Streams
ghci> import qualified Data.ByteString.Builder as Builder
ghci> :{
ghci| let r = Snap.Core.setResponseBody
ghci|         (out -> do
ghci|             Streams.write (Just $ Builder.byteString "Hello, world!") out
ghci|             return out)
ghci|         Snap.Core.emptyResponse
ghci| :}
ghci> assertBodyContains "^Hello" r
ghci> assertBodyContains "Bye" r
*** Exception: HUnitFailure "Expected body to match regexp \"\"Bye\"\", but didn't"

Getting response bodies

1 declaration

Dumping HTTP Messages

2 declarations