diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,5 +1,9 @@
 # Changelog for wai-extra
 
+## 3.0.29.1
+
+* Fix `Network.Wai.Test.request` always sending an empty request body [#794](https://github.com/yesodweb/wai/pull/794)
+
 ## 3.0.29
 
 * Export `Network.Wai.EventSource.eventStreamAppRaw` [#786](https://github.com/yesodweb/wai/pull/786)
diff --git a/Network/Wai/Test.hs b/Network/Wai/Test.hs
--- a/Network/Wai/Test.hs
+++ b/Network/Wai/Test.hs
@@ -99,6 +99,10 @@
 data SRequest = SRequest
     { simpleRequest :: Request
     , simpleRequestBody :: L.ByteString
+    -- ^ Request body that will override the one set in 'simpleRequest'.
+    --
+    -- This is usually simpler than setting the body as a stateful IO-action
+    -- in 'simpleRequest'.
     }
 data SResponse = SResponse
     { simpleStatus :: H.Status
@@ -106,8 +110,16 @@
     , simpleBody :: L.ByteString
     }
     deriving (Show, Eq)
+
 request :: Request -> Session SResponse
-request = srequest . flip SRequest L.empty
+request req = do
+    app <- ask
+    req' <- addCookiesToRequest req
+    response <- liftIO $ do
+        ref <- newIORef $ error "runResponse gave no result"
+        ResponseReceived <- app req' (runResponse ref)
+        readIORef ref
+    extractSetCookieFromSResponse response
 
 -- | Set whole path (request path + query string).
 setPath :: Request -> S8.ByteString -> Request
@@ -166,22 +178,18 @@
        (Map.fromList [(Cookie.setCookieName c, c) | c <- newClientCookies ]))
   return response
 
+-- | Similar to 'request', but allows setting the request body as a plain
+-- 'L.ByteString'.
 srequest :: SRequest -> Session SResponse
 srequest (SRequest req bod) = do
-    app <- ask
     refChunks <- liftIO $ newIORef $ L.toChunks bod
-    let req' = req
-            { requestBody = atomicModifyIORef refChunks $ \bss ->
-                case bss of
-                    [] -> ([], S.empty)
-                    x:y -> (y, x)
-            }
-    req'' <- addCookiesToRequest req'
-    response <- liftIO $ do
-        ref <- newIORef $ error "runResponse gave no result"
-        ResponseReceived <- app req'' (runResponse ref)
-        readIORef ref
-    extractSetCookieFromSResponse response
+    request $
+      req
+        { requestBody = atomicModifyIORef refChunks $ \bss ->
+            case bss of
+                [] -> ([], S.empty)
+                x:y -> (y, x)
+        }
 
 runResponse :: IORef SResponse -> Response -> IO ResponseReceived
 runResponse ref res = do
diff --git a/example/Main.hs b/example/Main.hs
--- a/example/Main.hs
+++ b/example/Main.hs
@@ -42,10 +42,10 @@
     where
         handle counter emit flush = do
             threadDelay 1000000
-            emit $ ServerEvent (Just $ string8 "raw")
+            _ <- emit $ ServerEvent (Just $ string8 "raw")
                                Nothing
                                [string8 . show $ counter]
-            flush
+            _ <- flush
             handle (counter + 1) emit flush
 
 main :: IO ()
diff --git a/test/Network/Wai/TestSpec.hs b/test/Network/Wai/TestSpec.hs
--- a/test/Network/Wai/TestSpec.hs
+++ b/test/Network/Wai/TestSpec.hs
@@ -3,6 +3,8 @@
 
 import           Control.Monad (void)
 
+import qualified Data.IORef as IORef
+
 import qualified Data.Text.Encoding as TE
 
 import           Data.Time.Calendar (fromGregorian)
@@ -53,25 +55,53 @@
       it "sets rawQueryString to empty string" $ do
         rawQueryString (setPath defaultRequest "/foo/bar/baz") `shouldBe` ""
 
+  describe "srequest" $ do
+
+    let echoApp req respond = do
+          reqBody <- L8.fromStrict <$> getRequestBodyChunk req
+          let reqHeaders = requestHeaders req
+          respond $
+            responseLBS
+              status200
+              reqHeaders
+              reqBody
+
+    it "returns the response body    of an echo app" $ do
+      sresp <- flip runSession echoApp $
+        srequest $ SRequest defaultRequest "request body"
+      simpleBody sresp `shouldBe` "request body"
+
   describe "request" $ do
 
-    let simpleApp _req respond =
+    let echoApp req respond = do
+          reqBody <- L8.fromStrict <$> getRequestBodyChunk req
+          let reqHeaders = requestHeaders req
           respond $
             responseLBS
               status200
-              [("foo", "bar")]
-              "simple"
+              reqHeaders
+              reqBody
 
-    it "returns the status code      of a simple app on default request" $ do
-      sresp <- runSession (request defaultRequest) simpleApp
+    it "returns the status code      of an echo app on default request" $ do
+      sresp <- runSession (request defaultRequest) echoApp
       simpleStatus sresp `shouldBe` status200
 
-    it "returns the response body    of a simple app on default request" $ do
-      sresp <- runSession (request defaultRequest) simpleApp
-      simpleBody sresp `shouldBe` "simple"
+    it "returns the response body    of an echo app" $ do
+      bodyRef <- IORef.newIORef "request body"
+      let getBodyChunk = IORef.atomicModifyIORef bodyRef $ \leftover -> ("", leftover)
+      sresp <- flip runSession echoApp $
+        request $
+          defaultRequest
+            { requestBody = getBodyChunk
+            }
+      simpleBody sresp `shouldBe` "request body"
 
-    it "returns the response headers of a simple app on default request" $ do
-      sresp <- runSession (request defaultRequest) simpleApp
+    it "returns the response headers of an echo app" $ do
+      sresp <- flip runSession echoApp $
+        request $
+          defaultRequest
+            { requestHeaders = [("foo", "bar")]
+            }
       simpleHeaders sresp `shouldBe` [("foo", "bar")]
 
     let cookieApp req respond =
diff --git a/wai-extra.cabal b/wai-extra.cabal
--- a/wai-extra.cabal
+++ b/wai-extra.cabal
@@ -1,5 +1,5 @@
 Name:                wai-extra
-Version:             3.0.29
+Version:             3.0.29.1
 Synopsis:            Provides some basic WAI handlers and middleware.
 description:
   Provides basic WAI handler and middleware functionality:
