diff --git a/guide/src/qs_8.md b/guide/src/qs_8.md
index be08e755..7661b9ad 100644
--- a/guide/src/qs_8.md
+++ b/guide/src/qs_8.md
@@ -3,6 +3,43 @@
 Every application should be well tested and. Actix provides the tools to perform unit and
 integration tests.
 
+## Unit tests
+
+For unit testing actix provides request builder type and simple handler runner.
+[*TestRequest*](../actix_web/test/struct.TestRequest.html) implements builder-like pattern.
+You can generate `HttpRequest` instance with `finish()` method or you can
+run your handler with `run()` or `run_async()` methods.
+
+```rust
+# extern crate http;
+# extern crate actix_web;
+use http::{header, StatusCode};
+use actix_web::*;
+use actix_web::test::TestRequest;
+
+fn index(req: HttpRequest) -> HttpResponse {
+     if let Some(hdr) = req.headers().get(header::CONTENT_TYPE) {
+        if let Ok(s) = hdr.to_str() {
+            return httpcodes::HTTPOk.response()
+        }
+     }
+     httpcodes::HTTPBadRequest.response()
+}
+
+fn main() {
+    let resp = TestRequest::with_header("content-type", "text/plain")
+        .run(index)
+        .unwrap();
+    assert_eq!(resp.status(), StatusCode::OK);
+
+    let resp = TestRequest::default()
+        .run(index)
+        .unwrap();
+    assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
+}
+```
+
+
 ## Integration tests
 
 There are several methods how you can test your application. Actix provides 
diff --git a/src/application.rs b/src/application.rs
index 80b8173c..1de0ff5b 100644
--- a/src/application.rs
+++ b/src/application.rs
@@ -313,6 +313,7 @@ mod tests {
     use std::str::FromStr;
     use http::{Method, Version, Uri, HeaderMap, StatusCode};
     use super::*;
+    use test::TestRequest;
     use httprequest::HttpRequest;
     use httpcodes;
 
@@ -322,9 +323,7 @@ mod tests {
             .resource("/test", |r| r.h(httpcodes::HTTPOk))
             .finish();
 
-        let req = HttpRequest::new(
-            Method::GET, Uri::from_str("/test").unwrap(),
-            Version::HTTP_11, HeaderMap::new(), None);
+        let req = TestRequest::with_uri("/test").finish();
         let resp = app.run(req);
         assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK);
 
diff --git a/src/handler.rs b/src/handler.rs
index a5e34223..deab468e 100644
--- a/src/handler.rs
+++ b/src/handler.rs
@@ -412,6 +412,7 @@ impl<S> Handler<S> for NormalizePath {
 mod tests {
     use super::*;
     use http::{header, Method};
+    use test::TestRequest;
     use application::Application;
 
     fn index(_req: HttpRequest) -> HttpResponse {
@@ -438,7 +439,7 @@ mod tests {
                           ("/resource2/?p1=1&p2=2", "", StatusCode::OK)
         ];
         for (path, target, code) in params {
-            let req = app.prepare_request(HttpRequest::from_path(path));
+            let req = app.prepare_request(TestRequest::with_uri(path).finish());
             let resp = app.run(req);
             let r = resp.as_response().unwrap();
             assert_eq!(r.status(), code);
@@ -470,7 +471,7 @@ mod tests {
                           ("/resource2/?p1=1&p2=2", StatusCode::OK)
         ];
         for (path, code) in params {
-            let req = app.prepare_request(HttpRequest::from_path(path));
+            let req = app.prepare_request(TestRequest::with_uri(path).finish());
             let resp = app.run(req);
             let r = resp.as_response().unwrap();
             assert_eq!(r.status(), code);
@@ -501,7 +502,7 @@ mod tests {
             ("/////resource1/a//b/?p=1", "", StatusCode::NOT_FOUND),
         ];
         for (path, target, code) in params {
-            let req = app.prepare_request(HttpRequest::from_path(path));
+            let req = app.prepare_request(TestRequest::with_uri(path).finish());
             let resp = app.run(req);
             let r = resp.as_response().unwrap();
             assert_eq!(r.status(), code);
@@ -558,7 +559,7 @@ mod tests {
             ("/////resource2/a///b/?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY),
         ];
         for (path, target, code) in params {
-            let req = app.prepare_request(HttpRequest::from_path(path));
+            let req = app.prepare_request(TestRequest::with_uri(path).finish());
             let resp = app.run(req);
             let r = resp.as_response().unwrap();
             assert_eq!(r.status(), code);
diff --git a/src/httprequest.rs b/src/httprequest.rs
index cf79ab33..ddaa4e98 100644
--- a/src/httprequest.rs
+++ b/src/httprequest.rs
@@ -119,31 +119,6 @@ impl HttpRequest<()> {
         HttpRequest(msg, None, None)
     }
 
-    /// Construct a new Request.
-    #[inline]
-    #[cfg(test)]
-    pub fn from_path(path: &str) -> HttpRequest
-    {
-        use std::str::FromStr;
-
-        HttpRequest(
-            SharedHttpMessage::from_message(HttpMessage {
-                method: Method::GET,
-                uri: Uri::from_str(path).unwrap(),
-                version: Version::HTTP_11,
-                headers: HeaderMap::new(),
-                params: Params::default(),
-                cookies: None,
-                addr: None,
-                payload: None,
-                extensions: Extensions::new(),
-                info: None,
-            }),
-            None,
-            None,
-        )
-    }
-
     #[inline]
     /// Construct new http request with state.
     pub fn with_state<S>(self, state: Rc<S>, router: Router) -> HttpRequest<S> {
@@ -163,7 +138,7 @@ impl<S> HttpRequest<S> {
     // mutable reference should not be returned as result for request's method
     #[inline(always)]
     #[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref, inline_always))]
-    fn as_mut(&self) -> &mut HttpMessage {
+    pub(crate) fn as_mut(&self) -> &mut HttpMessage {
         self.0.get_mut()
     }
 
@@ -657,30 +632,25 @@ mod tests {
     use std::str::FromStr;
     use router::Pattern;
     use resource::Resource;
+    use test::TestRequest;
 
     #[test]
     fn test_debug() {
-        let req = HttpRequest::new(
-            Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, HeaderMap::new(), None);
+        let req = TestRequest::with_header("content-type", "text/plain").finish();
         let dbg = format!("{:?}", req);
         assert!(dbg.contains("HttpRequest"));
     }
 
     #[test]
     fn test_no_request_cookies() {
-        let req = HttpRequest::new(
-            Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, HeaderMap::new(), None);
+        let req = HttpRequest::default();
         assert!(req.cookies().unwrap().is_empty());
     }
 
     #[test]
     fn test_request_cookies() {
-        let mut headers = HeaderMap::new();
-        headers.insert(header::COOKIE,
-                       header::HeaderValue::from_static("cookie1=value1; cookie2=value2"));
-
-        let req = HttpRequest::new(
-            Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, headers, None);
+        let req = TestRequest::with_header(
+            header::COOKIE, "cookie1=value1; cookie2=value2").finish();
         {
             let cookies = req.cookies().unwrap();
             assert_eq!(cookies.len(), 2);
@@ -733,8 +703,7 @@ mod tests {
 
     #[test]
     fn test_request_match_info() {
-        let mut req = HttpRequest::new(Method::GET, Uri::from_str("/value/?id=test").unwrap(),
-                                       Version::HTTP_11, HeaderMap::new(), None);
+        let mut req = TestRequest::with_uri("/value/?id=test").finish();
 
         let mut resource = Resource::<()>::default();
         resource.name("index");
@@ -748,15 +717,10 @@ mod tests {
 
     #[test]
     fn test_chunked() {
-        let req = HttpRequest::new(
-            Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, HeaderMap::new(), None);
+        let req = HttpRequest::default();
         assert!(!req.chunked().unwrap());
 
-        let mut headers = HeaderMap::new();
-        headers.insert(header::TRANSFER_ENCODING,
-                       header::HeaderValue::from_static("chunked"));
-        let req = HttpRequest::new(
-            Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, headers, None);
+        let req = TestRequest::with_header(header::TRANSFER_ENCODING, "chunked").finish();
         assert!(req.chunked().unwrap());
 
         let mut headers = HeaderMap::new();
diff --git a/src/test/mod.rs b/src/test/mod.rs
index 247753b9..0d3c596f 100644
--- a/src/test/mod.rs
+++ b/src/test/mod.rs
@@ -1,17 +1,30 @@
 //! Various helpers for Actix applications to use during testing.
 
 use std::{net, thread};
+use std::rc::Rc;
 use std::sync::mpsc;
+use std::str::FromStr;
+use std::collections::HashMap;
 
 use actix::{Arbiter, SyncAddress, System, msgs};
+use cookie::Cookie;
+use http::{Uri, Method, Version, HeaderMap, HttpTryFrom};
+use http::header::{HeaderName, HeaderValue};
+use futures::Future;
 use tokio_core::net::TcpListener;
+use tokio_core::reactor::Core;
 
+use error::Error;
 use server::HttpServer;
-use handler::Handler;
+use handler::{Handler, Responder, ReplyItem};
 use channel::{HttpHandler, IntoHttpHandler};
 use middlewares::Middleware;
 use application::{Application, HttpApplication};
-
+use param::Params;
+use router::Router;
+use payload::Payload;
+use httprequest::HttpRequest;
+use httpresponse::HttpResponse;
 
 /// The `TestServer` type.
 ///
@@ -192,3 +205,188 @@ impl<S: 'static> Iterator for TestApp<S> {
         }
     }
 }
+
+/// Test `HttpRequest` builder
+///
+/// ```rust
+/// # extern crate http;
+/// # extern crate actix_web;
+/// # use http::{header, StatusCode};
+/// # use actix_web::*;
+/// use actix_web::test::TestRequest;
+///
+/// fn index(req: HttpRequest) -> HttpResponse {
+///     if let Some(hdr) = req.headers().get(header::CONTENT_TYPE) {
+///         httpcodes::HTTPOk.response()
+///     } else {
+///         httpcodes::HTTPBadRequest.response()
+///     }
+/// }
+///
+/// fn main() {
+///     let resp = TestRequest::with_header("content-type", "text/plain")
+///         .run(index).unwrap();
+///     assert_eq!(resp.status(), StatusCode::OK);
+///
+///     let resp = TestRequest::default()
+///         .run(index).unwrap();
+///     assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
+/// }
+/// ```
+pub struct TestRequest<S> {
+    state: S,
+    version: Version,
+    method: Method,
+    uri: Uri,
+    headers: HeaderMap,
+    params: Params<'static>,
+    cookies: Option<Vec<Cookie<'static>>>,
+    payload: Option<Payload>,
+}
+
+impl Default for TestRequest<()> {
+
+    fn default() -> TestRequest<()> {
+        TestRequest {
+            state: (),
+            method: Method::GET,
+            uri: Uri::from_str("/").unwrap(),
+            version: Version::HTTP_11,
+            headers: HeaderMap::new(),
+            params: Params::default(),
+            cookies: None,
+            payload: None,
+        }
+    }
+}
+
+impl TestRequest<()> {
+
+    /// Create TestReqeust and set request uri
+    pub fn with_uri(path: &str) -> TestRequest<()> {
+        TestRequest::default().uri(path)
+    }
+
+    /// Create TestReqeust and set header
+    pub fn with_header<K, V>(key: K, value: V) -> TestRequest<()>
+        where HeaderName: HttpTryFrom<K>,
+              HeaderValue: HttpTryFrom<V>
+    {
+        TestRequest::default().header(key, value)
+    }
+}
+
+impl<S> TestRequest<S> {
+
+    /// Start HttpRequest build process with application state
+    pub fn with_state(state: S) -> TestRequest<S> {
+        TestRequest {
+            state: state,
+            method: Method::GET,
+            uri: Uri::from_str("/").unwrap(),
+            version: Version::HTTP_11,
+            headers: HeaderMap::new(),
+            params: Params::default(),
+            cookies: None,
+            payload: None,
+        }
+    }
+
+    /// Set HTTP version of this request
+    pub fn version(mut self, ver: Version) -> Self {
+        self.version = ver;
+        self
+    }
+
+    /// Set HTTP method of this request
+    pub fn method(mut self, meth: Method) -> Self {
+        self.method = meth;
+        self
+    }
+
+    /// Set HTTP Uri of this request
+    pub fn uri(mut self, path: &str) -> Self {
+        self.uri = Uri::from_str(path).unwrap();
+        self
+    }
+
+    /// Set a header
+    pub fn header<K, V>(mut self, key: K, value: V) -> Self
+        where HeaderName: HttpTryFrom<K>,
+              HeaderValue: HttpTryFrom<V>
+    {
+        if let Ok(key) = HeaderName::try_from(key) {
+            if let Ok(value) = HeaderValue::try_from(value) {
+                self.headers.append(key, value);
+                return self
+            }
+        }
+        panic!("Can not create header");
+    }
+
+    /// Set request path pattern parameter
+    pub fn param(mut self, name: &'static str, value: &'static str) -> Self {
+        self.params.add(name, value);
+        self
+    }
+
+    /// Complete request creation and generate `HttpRequest` instance
+    pub fn finish(self) -> HttpRequest<S> {
+        let TestRequest { state, method, uri, version, headers, params, cookies, payload } = self;
+        let req = HttpRequest::new(method, uri, version, headers, payload);
+        req.as_mut().cookies = cookies;
+        req.as_mut().params = params;
+        let (router, _) = Router::new::<S>("/", HashMap::new());
+        req.with_state(Rc::new(state), router)
+    }
+
+    /// This method generates `HttpRequest` instance and runs handler
+    /// with generated request.
+    ///
+    /// This method panics is handler returns actor or async result.
+    pub fn run<H: Handler<S>>(self, mut h: H) ->
+        Result<HttpResponse, <<H as Handler<S>>::Result as Responder>::Error>
+    {
+        let req = self.finish();
+        let resp = h.handle(req.clone());
+
+        match resp.respond_to(req.clone_without_state()) {
+            Ok(resp) => {
+                match resp.into().into() {
+                    ReplyItem::Message(resp) => Ok(resp),
+                    ReplyItem::Actor(_) => panic!("Actor handler is not supported."),
+                    ReplyItem::Future(_) => panic!("Async handler is not supported."),
+                }
+            },
+            Err(err) => Err(err),
+        }
+    }
+
+    /// This method generates `HttpRequest` instance and runs handler
+    /// with generated request.
+    ///
+    /// This method panics is handler returns actor.
+    pub fn run_async<H, R, F, E>(self, h: H) -> Result<HttpResponse, E>
+        where H: Fn(HttpRequest<S>) -> F + 'static,
+              F: Future<Item=R, Error=E> + 'static,
+              R: Responder<Error=E> + 'static,
+              E: Into<Error> + 'static
+    {
+        let req = self.finish();
+        let fut = h(req.clone());
+
+        let mut core = Core::new().unwrap();
+        match core.run(fut) {
+            Ok(r) => {
+                match r.respond_to(req.clone_without_state()) {
+                    Ok(reply) => match reply.into().into() {
+                        ReplyItem::Message(resp) => Ok(resp),
+                        _ => panic!("Nested async replies are not supported"),
+                    },
+                    Err(e) => Err(e),
+                }
+            },
+            Err(err) => Err(err),
+        }
+    }
+}