diff --git a/hspec-wai.cabal b/hspec-wai.cabal
--- a/hspec-wai.cabal
+++ b/hspec-wai.cabal
@@ -1,5 +1,5 @@
 name:             hspec-wai
-version:          0.6.0
+version:          0.6.1
 license:          MIT
 license-file:     LICENSE
 copyright:        (c) 2012-2014 Fujimura Daisuke,
@@ -9,8 +9,8 @@
 build-type:       Simple
 cabal-version:    >= 1.8
 category:         Testing
-synopsis:         Experimental Hspec support for testing WAI applications (depends on hspec2!)
-description:      Experimental Hspec support for testing WAI applications (depends on hspec2!)
+synopsis:         Experimental Hspec support for testing WAI applications
+description:      Experimental Hspec support for testing WAI applications
 
 source-repository head
   type: git
@@ -29,7 +29,7 @@
       Test.Hspec.Wai.Matcher
   build-depends:
       base == 4.*
-    , bytestring
+    , bytestring >= 0.10
     , text
     , transformers
     , case-insensitive
@@ -61,3 +61,4 @@
     , hspec-expectations
 
     , hspec
+    , QuickCheck
diff --git a/src/Test/Hspec/Wai.hs b/src/Test/Hspec/Wai.hs
--- a/src/Test/Hspec/Wai.hs
+++ b/src/Test/Hspec/Wai.hs
@@ -14,6 +14,9 @@
 , delete
 , request
 
+-- ** Posting HTML forms
+, postHtmlForm
+
 -- * Matching on the response
 , shouldRespondWith
 , ResponseMatcher(..)
@@ -41,6 +44,7 @@
 import           Test.Hspec.Core.Hooks
 import           Test.Hspec.Expectations (expectationFailure)
 
+import           Test.Hspec.Wai.Util
 import           Test.Hspec.Wai.Internal
 import           Test.Hspec.Wai.Matcher
 
@@ -121,3 +125,12 @@
 request method path headers body = getApp >>= liftIO . runSession (Wai.srequest $ SRequest req body)
   where
     req = setPath defaultRequest {requestMethod = method, requestHeaders = headers} path
+
+-- | Perform a @POST@ request to the application under test.
+--
+-- The specified list of key-value pairs is encoded as
+-- @application/x-www-form-urlencoded@ and used as request body.
+--
+-- In additon the @Content-Type@ is set to @application/x-www-form-urlencoded@.
+postHtmlForm :: ByteString -> [(String, String)] -> WaiSession SResponse
+postHtmlForm path = request methodPost path [(hContentType, "application/x-www-form-urlencoded")] . formUrlEncodeQuery
diff --git a/src/Test/Hspec/Wai/Util.hs b/src/Test/Hspec/Wai/Util.hs
--- a/src/Test/Hspec/Wai/Util.hs
+++ b/src/Test/Hspec/Wai/Util.hs
@@ -4,17 +4,23 @@
 import           Control.Monad
 import           Data.Monoid
 import           Data.Maybe
-import           Data.Char
+import           Data.List
+import           Data.Word
+import           Data.Char hiding (ord)
+import qualified Data.Char as Char
 import           Data.ByteString (ByteString)
-import qualified Data.ByteString.Char8 as B
+import qualified Data.ByteString as B
+import qualified Data.ByteString.Char8 as B8
 import qualified Data.ByteString.Lazy as LB
+import           Data.ByteString.Lazy.Builder (Builder)
+import qualified Data.ByteString.Lazy.Builder as Builder
 import qualified Data.Text as T
 import qualified Data.Text.Encoding as T
 import qualified Data.CaseInsensitive as CI
 import           Network.HTTP.Types
 
 formatHeader :: Header -> String
-formatHeader header@(name, value) = "  " ++ fromMaybe (show header) (safeToString $ B.concat [CI.original name, ": ", value])
+formatHeader header@(name, value) = "  " ++ fromMaybe (show header) (safeToString $ B8.concat [CI.original name, ": ", value])
 
 safeToString :: ByteString -> Maybe String
 safeToString bs = do
@@ -27,3 +33,57 @@
 -- for compatibility with older versions of `bytestring`
 toStrict :: LB.ByteString -> ByteString
 toStrict = mconcat . LB.toChunks
+
+formUrlEncodeQuery :: [(String, String)] -> LB.ByteString
+formUrlEncodeQuery = Builder.toLazyByteString . mconcat . intersperse amp . map encodePair
+  where
+    equals = Builder.word8 (ord '=')
+    amp = Builder.word8 (ord '&')
+    percent = Builder.word8 (ord '%')
+    plus = Builder.word8 (ord '+')
+
+    encodePair :: (String, String) -> Builder
+    encodePair (key, value) = encode key <> equals <> encode value
+
+    encode :: String -> Builder
+    encode = escape . T.encodeUtf8 . T.pack . newlineNormalize
+
+    newlineNormalize :: String -> String
+    newlineNormalize input = case input of
+      [] -> []
+      '\n' : xs -> '\r' : '\n': newlineNormalize xs
+      x : xs -> x : newlineNormalize xs
+
+    escape :: ByteString -> Builder
+    escape = mconcat . map f . B.unpack
+      where
+        f :: Word8 -> Builder
+        f c
+          | p c = Builder.word8 c
+          | c == ord ' ' = plus
+          | otherwise = percentEncode c
+
+        p :: Word8 -> Bool
+        p c =
+             ord 'a' <= c && c <= ord 'z'
+          || c == ord '_'
+          || c == ord '*'
+          || c == ord '-'
+          || c == ord '.'
+          || ord '0' <= c && c <= ord '9'
+          || ord 'A' <= c && c <= ord 'Z'
+
+    ord :: Char -> Word8
+    ord = fromIntegral . Char.ord
+
+    percentEncode :: Word8 -> Builder
+    percentEncode n = percent <> hex hi <> hex lo
+      where
+        (hi, lo) = n `divMod` 16
+
+    hex :: Word8 -> Builder
+    hex n = Builder.word8 (offset + n)
+      where
+        offset
+          | n < 10    = 48
+          | otherwise = 55
