Repository: gavv/httpexpect
Pull request #75 (2020-05-21 15:50:59)

Websocket unit tests
As requested in #70 more unit tests for websockets are added. New tests cover cases where gorillamux websocket Conn type is not needed or empty one is sufficient.

coveralls: 
[![Coverage Status](https://coveralls.io/builds/30947992/badge)](https://coveralls.io/builds/30947992)

Coverage increased (+1.5%) to 95.448% when pulling **682e7146473b01a4f04a34e9f59a388663a24285 on nio6412:ws-unit-tests** into **4cfa969e232100a06000d730abb34c300e54f9cb on gavv:master**.

gavv: FYI: https://github.com/gavv/httpexpect/issues/70#issuecomment-638041212

Patch:
diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml
new file mode 100644
index 0000000..ca8eed1
--- /dev/null
+++ b/.github/workflows/build.yaml
@@ -0,0 +1,58 @@
+name: build
+
+on:
+  pull_request:
+    branches:
+      - master
+      - v*
+
+  push:
+    branches:
+      - master
+      - v*
+    tags:
+      - v*
+
+jobs:
+  build:
+    runs-on: ubuntu-latest
+
+    strategy:
+      matrix:
+        go: [ '1.14', '1.15' ]
+
+    name: Go ${{ matrix.go }}
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v2
+
+      - name: Install Go
+        uses: actions/setup-go@v1
+        with:
+          go-version: ${{ matrix.go }}
+
+      - name: Build
+        run: go get -v .
+
+      - name: Run tests
+        run: go test -covermode=count -coverprofile=coverage.out
+
+      - name: Run linters
+        uses: golangci/golangci-lint-action@v2
+        with:
+          version: v1.28
+
+      - name: Build examples
+        run: cd _examples && go get -v .
+
+      - name: Run examples tests
+        run: cd _examples && go test
+
+      - name: Prepare coverage report
+        uses: jandelgado/gcov2lcov-action@v1.0.5
+
+      - name: Send coverage report
+        uses: coverallsapp/github-action@master
+        with:
+          github-token: ${{ secrets.GITHUB_TOKEN }}
+          path-to-lcov: coverage.lcov
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 2dc92ea..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,24 +0,0 @@
-language: go
-sudo: false
-
-go:
-  - 1.12.x
-  - 1.14.x
-
-env:
-  - GO111MODULE=on
-
-before_install:
-  - curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.24.0
-  - go get golang.org/x/tools/cmd/cover
-  - go get github.com/mattn/goveralls
-
-script:
-  - go get -v .
-  - go test -coverprofile profile.cov
-  - ${GOPATH}/bin/golangci-lint run .
-
-  - ( cd _examples && go get -v . )
-  - ( cd _examples && go test )
-
-  - ${GOPATH}/bin/goveralls -coverprofile profile.cov -service=travis-ci
diff --git a/README.md b/README.md
index 933ffbe..0c93562 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# httpexpect [![GoDoc](https://godoc.org/github.com/gavv/httpexpect?status.svg)](https://godoc.org/github.com/gavv/httpexpect) [![Travis](https://img.shields.io/travis/gavv/httpexpect.svg)](https://travis-ci.org/gavv/httpexpect) [![Coveralls](https://coveralls.io/repos/github/gavv/httpexpect/badge.svg?branch=master)](https://coveralls.io/github/gavv/httpexpect?branch=master)
+# httpexpect [![GoDev](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white)](https://pkg.go.dev/github.com/gavv/httpexpect) [![Build](https://github.com/gavv/httpexpect/workflows/build/badge.svg)](https://github.com/gavv/httpexpect/actions) [![Coveralls](https://coveralls.io/repos/github/gavv/httpexpect/badge.svg?branch=master)](https://coveralls.io/github/gavv/httpexpect?branch=master) [![GitHub release](https://img.shields.io/github/tag/gavv/httpexpect.svg)](https://github.com/gavv/httpexpect/releases)
 
 Concise, declarative, and easy to use end-to-end HTTP and REST API testing for Go (golang).
 
@@ -407,26 +407,6 @@ e := httpexpect.WithConfig(httpexpect.Config{
 })
 ```
 
-##### Session support
-
-```go
-// cookie jar is used to store cookies from server
-e := httpexpect.WithConfig(httpexpect.Config{
-	Reporter: httpexpect.NewAssertReporter(t),
-	Client: &http.Client{
-		Jar: httpexpect.NewJar(), // used by default if Client is nil
-	},
-})
-
-// cookies are disabled
-e := httpexpect.WithConfig(httpexpect.Config{
-	Reporter: httpexpect.NewAssertReporter(t),
-	Client: &http.Client{
-		Jar: nil,
-	},
-})
-```
-
 ##### Use HTTP handler directly
 
 ```go
@@ -475,6 +455,54 @@ e.GET("/path").WithHandler(handler).
 	Status(http.StatusOK)
 ```
 
+##### Session support
+
+```go
+// cookie jar is used to store cookies from server
+e := httpexpect.WithConfig(httpexpect.Config{
+	Reporter: httpexpect.NewAssertReporter(t),
+	Client: &http.Client{
+		Jar: httpexpect.NewJar(), // used by default if Client is nil
+	},
+})
+
+// cookies are disabled
+e := httpexpect.WithConfig(httpexpect.Config{
+	Reporter: httpexpect.NewAssertReporter(t),
+	Client: &http.Client{
+		Jar: nil,
+	},
+})
+```
+
+##### TLS support
+
+```go
+// use TLS with http.Transport
+e := httpexpect.WithConfig(httpexpect.Config{
+	Reporter: httpexpect.NewAssertReporter(t),
+	Client: &http.Client{
+		Transport: &http.Transport{
+			TLSClientConfig: &tls.Config{
+				// accept any certificate; for testing only!
+				InsecureSkipVerify: true,
+			},
+		},
+	},
+})
+
+// use TLS with http.Handler
+e := httpexpect.WithConfig(httpexpect.Config{
+	Reporter: httpexpect.NewAssertReporter(t),
+	Client: &http.Client{
+		Transport: &httpexpect.Binder{
+			Handler: myHandler,
+			TLS:     &tls.ConnectionState{},
+		},
+	},
+})
+```
+
 ## Similar packages
 
 * [`gorequest`](https://github.com/parnurzeal/gorequest)
diff --git a/_examples/go.mod b/_examples/go.mod
index cf120a2..1fd087e 100644
--- a/_examples/go.mod
+++ b/_examples/go.mod
@@ -1,10 +1,10 @@
 module examples
 
-go 1.12
+go 1.14
 
 require (
 	github.com/dgrijalva/jwt-go v3.0.0+incompatible
-	github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072
+	github.com/fasthttp/websocket v1.4.2
 	github.com/gavv/httpexpect/v2 v2.0.3
 	github.com/gin-gonic/gin v1.5.0
 	github.com/gorilla/websocket v1.4.2
@@ -15,7 +15,7 @@ require (
 	github.com/moul/http2curl v1.0.0 // indirect
 	github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
 	github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 // indirect
-	github.com/valyala/fasthttp v1.0.0
+	github.com/valyala/fasthttp v1.9.0
 	google.golang.org/appengine v1.0.0
 	gopkg.in/yaml.v2 v2.2.8 // indirect
 )
diff --git a/_examples/go.sum b/_examples/go.sum
index fcd748c..6e09a2e 100644
--- a/_examples/go.sum
+++ b/_examples/go.sum
@@ -32,6 +32,8 @@ github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZi
 github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw=
 github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072 h1:DddqAaWDpywytcG8w/qoQ5sAN8X12d3Z3koB0C3Rxsc=
 github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8=
+github.com/fasthttp/websocket v1.4.2 h1:AU/zSiIIAuJjBMf5o+vO0syGOnEfvZRu40xIhW/3RuM=
+github.com/fasthttp/websocket v1.4.2/go.mod h1:smsv/h4PBEBaU0XDTY5UwJTpZv69fQ0FfcLJr21mA6Y=
 github.com/fatih/structs v1.0.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
 github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
 github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
@@ -107,12 +109,15 @@ github.com/kataras/sitemap v0.0.5/go.mod h1:KY2eugMKiPwsJgx7+U103YZehfvNGOXURubc
 github.com/klauspost/compress v1.4.0 h1:8nsMz3tWa9SWWPL60G1V6CUsf4lLjWLTNEtibhe8gh8=
 github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
 github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
+github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
+github.com/klauspost/compress v1.8.6/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
 github.com/klauspost/compress v1.9.7 h1:hYW1gP94JUmAhBtJ+LNz5My+gBobDxPR1iVuKug26aA=
 github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
 github.com/klauspost/compress v1.9.8 h1:VMAMUUOh+gaxKTMk+zqbjsSjsIcUcL/LF4o63i82QyA=
 github.com/klauspost/compress v1.9.8/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
 github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e h1:+lIPJOWl+jSiJOc70QXJ07+2eg2Jy2EC7Mi11BWujeM=
 github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
+github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
 github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
@@ -159,6 +164,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
 github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
 github.com/ryanuber/columnize v2.1.0+incompatible h1:j1Wcmh8OrK4Q7GXY+V7SVSY8nUWQxHW5TkBe7YUl+2s=
 github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
+github.com/savsgio/gotils v0.0.0-20200117113501-90175b0fbe3f h1:PgA+Olipyj258EIEYnpFFONrrCcAIWNUNoFhUfMqAGY=
+github.com/savsgio/gotils v0.0.0-20200117113501-90175b0fbe3f/go.mod h1:lHhJedqxCoHN+zMtwGNTXWmF0u9Jt363FYRhV6g0CdY=
 github.com/schollz/closestmatch v2.1.0+incompatible h1:Uel2GXEpJqOWBrlyI+oY9LTiyyjYS17cCYRqP13/SHk=
 github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g=
 github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
@@ -191,6 +198,8 @@ github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6Kllzaw
 github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
 github.com/valyala/fasthttp v1.0.0 h1:BwIoZQbBsTo3v2F5lz5Oy3TlTq4wLKTLV260EVTEWco=
 github.com/valyala/fasthttp v1.0.0/go.mod h1:4vX61m6KN+xDduDNwXrhIAVZaZaZiQ1luJk8LWSxF3s=
+github.com/valyala/fasthttp v1.9.0 h1:hNpmUdy/+ZXYpGy0OBfm7K0UQTzb73W0T0U4iJIVrMw=
+github.com/valyala/fasthttp v1.9.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w=
 github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8WdUSz8=
 github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
 github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
@@ -223,6 +232,7 @@ golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 h1:efeOvDhwQ29Dj3SdAV/MJf8oukgn+8D8WgaCaRMchF8=
 golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
diff --git a/_examples/websocket.go b/_examples/websocket.go
index cf25e2c..334debe 100644
--- a/_examples/websocket.go
+++ b/_examples/websocket.go
@@ -5,7 +5,7 @@ import (
 
 	"github.com/gorilla/websocket"
 
-	fastwebsocket "github.com/fasthttp-contrib/websocket"
+	fastwebsocket "github.com/fasthttp/websocket"
 	"github.com/valyala/fasthttp"
 )
 
@@ -32,7 +32,8 @@ func WsHttpHandler(w http.ResponseWriter, r *http.Request) {
 // WsFastHandler is a simple fasthttp.RequestHandler that implements
 // WebSocket echo server.
 func WsFastHandler(ctx *fasthttp.RequestCtx) {
-	upgrader := fastwebsocket.New(func(c *fastwebsocket.Conn) {
+	var upgrader fastwebsocket.FastHTTPUpgrader
+	err := upgrader.Upgrade(ctx, func(c *fastwebsocket.Conn) {
 		defer c.Close()
 		for {
 			mt, message, err := c.ReadMessage()
@@ -45,7 +46,6 @@ func WsFastHandler(ctx *fasthttp.RequestCtx) {
 			}
 		}
 	})
-	err := upgrader.Upgrade(ctx)
 	if err != nil {
 		panic(err)
 	}
diff --git a/go.mod b/go.mod
index 32797a4..be38db0 100644
--- a/go.mod
+++ b/go.mod
@@ -1,10 +1,10 @@
 module github.com/gavv/httpexpect/v2
 
-go 1.12
+go 1.14
 
 require (
 	github.com/ajg/form v1.5.1
-	github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072
+	github.com/fasthttp/websocket v1.4.2
 	github.com/fatih/structs v1.0.0
 	github.com/google/go-querystring v1.0.0
 	github.com/gorilla/websocket v1.0.0
@@ -15,7 +15,7 @@ require (
 	github.com/onsi/gomega v1.7.0 // indirect
 	github.com/sergi/go-diff v1.0.0 // indirect
 	github.com/stretchr/testify v1.3.0
-	github.com/valyala/fasthttp v1.0.0
+	github.com/valyala/fasthttp v1.9.0
 	github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
 	github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
 	github.com/xeipuuv/gojsonschema v1.1.0
@@ -23,7 +23,7 @@ require (
 	github.com/yudai/gojsondiff v1.0.0
 	github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect
 	github.com/yudai/pp v2.0.1+incompatible // indirect
-	golang.org/x/net v0.0.0-20190724013045-ca1201d0de80
+	golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297
 	gopkg.in/yaml.v2 v2.2.2 // indirect
 	moul.io/http2curl v1.0.1-0.20190925090545-5cd742060b0e
 )
diff --git a/go.sum b/go.sum
index 970f975..9accef5 100644
--- a/go.sum
+++ b/go.sum
@@ -2,8 +2,8 @@ github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
 github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
 github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072 h1:DddqAaWDpywytcG8w/qoQ5sAN8X12d3Z3koB0C3Rxsc=
-github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8=
+github.com/fasthttp/websocket v1.4.2 h1:AU/zSiIIAuJjBMf5o+vO0syGOnEfvZRu40xIhW/3RuM=
+github.com/fasthttp/websocket v1.4.2/go.mod h1:smsv/h4PBEBaU0XDTY5UwJTpZv69fQ0FfcLJr21mA6Y=
 github.com/fatih/structs v1.0.0 h1:BrX964Rv5uQ3wwS+KRUAJCBBw5PQmgJfJ6v4yly5QwU=
 github.com/fatih/structs v1.0.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
 github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
@@ -20,10 +20,10 @@ github.com/imkira/go-interpol v1.0.0 h1:HrmLyvOLJyjR0YofMw8QGdCIuYOs4TJUBDNU5sJC
 github.com/imkira/go-interpol v1.0.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA=
 github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 h1:uC1QfSlInpQF+M0ao65imhwqKnz3Q2z/d8PWZRMQvDM=
 github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
-github.com/klauspost/compress v1.4.0 h1:8nsMz3tWa9SWWPL60G1V6CUsf4lLjWLTNEtibhe8gh8=
-github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
-github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e h1:+lIPJOWl+jSiJOc70QXJ07+2eg2Jy2EC7Mi11BWujeM=
-github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
+github.com/klauspost/compress v1.8.2 h1:Bx0qjetmNjdFXASH02NSAREKpiaDwkO1DRZ3dV2KCcs=
+github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
+github.com/klauspost/cpuid v1.2.1 h1:vJi+O/nMdFt0vqm8NZBI6wzALWdA2X+egi0ogNyrC/w=
+github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
 github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
 github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
 github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
@@ -35,6 +35,8 @@ github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME=
 github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/savsgio/gotils v0.0.0-20200117113501-90175b0fbe3f h1:PgA+Olipyj258EIEYnpFFONrrCcAIWNUNoFhUfMqAGY=
+github.com/savsgio/gotils v0.0.0-20200117113501-90175b0fbe3f/go.mod h1:lHhJedqxCoHN+zMtwGNTXWmF0u9Jt363FYRhV6g0CdY=
 github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
 github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -42,8 +44,8 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
 github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
-github.com/valyala/fasthttp v1.0.0 h1:BwIoZQbBsTo3v2F5lz5Oy3TlTq4wLKTLV260EVTEWco=
-github.com/valyala/fasthttp v1.0.0/go.mod h1:4vX61m6KN+xDduDNwXrhIAVZaZaZiQ1luJk8LWSxF3s=
+github.com/valyala/fasthttp v1.9.0 h1:hNpmUdy/+ZXYpGy0OBfm7K0UQTzb73W0T0U4iJIVrMw=
+github.com/valyala/fasthttp v1.9.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w=
 github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
 github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
 github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
@@ -62,9 +64,8 @@ github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZ
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 h1:Ao/3l156eZf2AW5wK8a7/smtodRU+gha3+BeqJ69lRk=
-golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM=
+golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
diff --git a/websocket.go b/websocket.go
index 0cfc218..ea7cdf4 100644
--- a/websocket.go
+++ b/websocket.go
@@ -16,19 +16,29 @@ var infiniteTime = time.Time{}
 type Websocket struct {
 	config       Config
 	chain        chain
-	conn         *websocket.Conn
+	conn         WebsocketConn
 	readTimeout  time.Duration
 	writeTimeout time.Duration
 	isClosed     bool
 }
 
+// WebsocketConn is used by Websocket to communicate with actual websocket connection.
+type WebsocketConn interface {
+	ReadMessage() (messageType int, p []byte, err error)
+	WriteMessage(messageType int, data []byte) error
+	Close() error
+	SetReadDeadline(t time.Time) error
+	SetWriteDeadline(t time.Time) error
+	Subprotocol() string
+}
+
 // NewWebsocket returns a new Websocket given a Config with Reporter and
-// Printers, and websocket.Conn to be inspected and handled.
-func NewWebsocket(config Config, conn *websocket.Conn) *Websocket {
+// Printers, and Websocket to be inspected and handled.
+func NewWebsocket(config Config, conn WebsocketConn) *Websocket {
 	return makeWebsocket(config, makeChain(config.Reporter), conn)
 }
 
-func makeWebsocket(config Config, chain chain, conn *websocket.Conn) *Websocket {
+func makeWebsocket(config Config, chain chain, conn WebsocketConn) *Websocket {
 	return &Websocket{
 		config: config,
 		chain:  chain,
@@ -36,9 +46,9 @@ func makeWebsocket(config Config, chain chain, conn *websocket.Conn) *Websocket
 	}
 }
 
-// Raw returns underlying websocket.Conn object.
+// Raw returns underlying WebsocketConn object.
 // This is the value originally passed to NewConnection.
-func (c *Websocket) Raw() *websocket.Conn {
+func (c *Websocket) Raw() WebsocketConn {
 	return c.conn
 }
 
@@ -92,9 +102,6 @@ func (c *Websocket) Expect() *WebsocketMessage {
 	switch {
 	case c.chain.failed():
 		return makeWebsocketMessage(c.chain)
-	case c.conn == nil:
-		c.chain.fail("\nunexpected read from failed WebSocket connection")
-		return makeWebsocketMessage(c.chain)
 	case c.isClosed:
 		c.chain.fail("\nunexpected read from closed WebSocket connection")
 		return makeWebsocketMessage(c.chain)
@@ -389,10 +396,6 @@ func (c *Websocket) checkUnusable(where string) bool {
 	switch {
 	case c.chain.failed():
 		return true
-	case c.conn == nil:
-		c.chain.fail("\nunexpected %s call for failed WebSocket connection",
-			where)
-		return true
 	case c.isClosed:
 		c.chain.fail("\nunexpected %s call for closed WebSocket connection",
 			where)


Test patch:
diff --git a/e2e_websocket_test.go b/e2e_websocket_test.go
index db37325..0ea1f73 100644
--- a/e2e_websocket_test.go
+++ b/e2e_websocket_test.go
@@ -6,7 +6,7 @@ import (
 	"testing"
 	"time"
 
-	fastwebsocket "github.com/fasthttp-contrib/websocket"
+	fastwebsocket "github.com/fasthttp/websocket"
 	"github.com/gorilla/websocket"
 	"github.com/valyala/fasthttp"
 )
@@ -48,7 +48,8 @@ func createWebsocketHandler(opts wsHandlerOpts) http.Handler {
 }
 
 func websocketFastHandler(ctx *fasthttp.RequestCtx) {
-	upgrader := fastwebsocket.New(func(c *fastwebsocket.Conn) {
+	var upgrader fastwebsocket.FastHTTPUpgrader
+	err := upgrader.Upgrade(ctx, func(c *fastwebsocket.Conn) {
 		defer c.Close()
 		for {
 			mt, message, err := c.ReadMessage()
@@ -61,7 +62,6 @@ func websocketFastHandler(ctx *fasthttp.RequestCtx) {
 			}
 		}
 	})
-	err := upgrader.Upgrade(ctx)
 	if err != nil {
 		panic(err)
 	}
diff --git a/mocks_test.go b/mocks_test.go
index 4398b5d..2dbacad 100644
--- a/mocks_test.go
+++ b/mocks_test.go
@@ -3,6 +3,7 @@ package httpexpect
 import (
 	"net/http"
 	"testing"
+	"time"
 )
 
 type mockClient struct {
@@ -34,3 +35,103 @@ func (r *mockReporter) Errorf(message string, args ...interface{}) {
 	r.testing.Logf("Fail: "+message, args...)
 	r.reported = true
 }
+
+type mockWebsocketConn struct {
+	msgType      int
+	readMsgErr   error
+	writeMsgErr  error
+	closeError   error
+	readDlError  error
+	writeDlError error
+	msg          []byte
+	subprotocol  string
+}
+
+func newMockWebsocketConn() *mockWebsocketConn {
+	return &mockWebsocketConn{}
+}
+
+func (wc *mockWebsocketConn) WithWriteMsgError(retError error) *mockWebsocketConn {
+	wc.writeMsgErr = retError
+	return wc
+}
+
+func (wc *mockWebsocketConn) WithReadMsgError(retError error) *mockWebsocketConn {
+	wc.readMsgErr = retError
+	return wc
+}
+
+func (wc *mockWebsocketConn) WithWriteDlError(retError error) *mockWebsocketConn {
+	wc.writeDlError = retError
+	return wc
+}
+
+func (wc *mockWebsocketConn) WithReadDlError(retError error) *mockWebsocketConn {
+	wc.readDlError = retError
+	return wc
+}
+
+func (wc *mockWebsocketConn) WithCloseError(retError error) *mockWebsocketConn {
+	wc.closeError = retError
+	return wc
+}
+
+func (wc *mockWebsocketConn) WithMsgType(msgType int) *mockWebsocketConn {
+	wc.msgType = msgType
+	return wc
+}
+
+func (wc *mockWebsocketConn) WithSubprotocol(subprotocol string) *mockWebsocketConn {
+	wc.subprotocol = subprotocol
+	return wc
+}
+
+func (wc *mockWebsocketConn) WithMessage(msg []byte) *mockWebsocketConn {
+	wc.msg = msg
+	return wc
+}
+
+func (wc *mockWebsocketConn) ReadMessage() (messageType int, p []byte, err error) {
+	return wc.msgType, []byte{}, wc.readMsgErr
+}
+func (wc *mockWebsocketConn) WriteMessage(messageType int, data []byte) error {
+	return wc.writeMsgErr
+
+}
+func (wc *mockWebsocketConn) Close() error {
+	return wc.closeError
+}
+func (wc *mockWebsocketConn) SetReadDeadline(t time.Time) error {
+	return wc.readDlError
+}
+func (wc *mockWebsocketConn) SetWriteDeadline(t time.Time) error {
+	return wc.writeDlError
+}
+func (wc *mockWebsocketConn) Subprotocol() string {
+	return wc.subprotocol
+}
+
+type MockWsPrinter struct {
+	isWrittenTo bool
+	isReadFrom  bool
+}
+
+func newMockWsPrinter() *MockWsPrinter {
+	return &MockWsPrinter{
+		isWrittenTo: false,
+		isReadFrom:  false,
+	}
+}
+
+func (pr *MockWsPrinter) Request(*http.Request) {}
+
+// Response is called after response is received.
+func (pr *MockWsPrinter) Response(*http.Response, time.Duration) {}
+
+func (pr *MockWsPrinter) WebsocketWrite(typ int, content []byte, closeCode int) {
+	pr.isWrittenTo = true
+}
+
+func (pr *MockWsPrinter) WebsocketRead(typ int, content []byte, closeCode int) {
+	pr.isReadFrom = true
+}
diff --git a/websocket_test.go b/websocket_test.go
index 499e118..83de7d6 100644
--- a/websocket_test.go
+++ b/websocket_test.go
@@ -1,19 +1,21 @@
 package httpexpect
 
 import (
+	"fmt"
 	"testing"
+	"time"
 
 	"github.com/gorilla/websocket"
 )
 
+func noWsPreSteps(ws *Websocket) {}
+
 func TestWebsocketFailed(t *testing.T) {
 	chain := makeChain(newMockReporter(t))
 
 	chain.fail("fail")
 
-	ws := &Websocket{
-		chain: chain,
-	}
+	ws := makeWebsocket(Config{}, chain, nil)
 
 	ws.chain.assertFailed(t)
 
@@ -40,15 +42,957 @@ func TestWebsocketFailed(t *testing.T) {
 	ws.Disconnect()
 }
 
-func TestWebsocketNil(t *testing.T) {
+func TestWebsocketExpect(t *testing.T) {
+	failedChain := makeChain(newMockReporter(t))
+	failedChain.fail("some previous fail...")
+
+	type args struct {
+		config     Config
+		chain      chain
+		wsConn     WebsocketConn
+		wsPreSteps func(*Websocket)
+	}
+	tests := []struct {
+		name     string
+		args     args
+		assertOk bool
+	}{
+		{
+			name: "success",
+			args: args{
+				config:     Config{},
+				chain:      makeChain(newMockReporter(t)),
+				wsPreSteps: noWsPreSteps,
+				wsConn:     newMockWebsocketConn(),
+			},
+			assertOk: true,
+		},
+		{
+			name: "fail to read message from conn",
+			args: args{
+				config:     Config{},
+				chain:      makeChain(newMockReporter(t)),
+				wsPreSteps: noWsPreSteps,
+				wsConn: newMockWebsocketConn().WithReadMsgError(
+					fmt.Errorf("failed to read message")),
+			},
+			assertOk: false,
+		},
+		{
+			name: "chain already failed",
+			args: args{
+				config:     Config{},
+				chain:      failedChain,
+				wsPreSteps: noWsPreSteps,
+				wsConn:     newMockWebsocketConn(),
+			},
+			assertOk: false,
+		},
+		{
+			name: "connection closed",
+			args: args{
+				config: Config{},
+				chain:  makeChain(newMockReporter(t)),
+				wsPreSteps: func(ws *Websocket) {
+					ws.Disconnect()
+				},
+				wsConn: newMockWebsocketConn(),
+			},
+			assertOk: false,
+		},
+		{
+			name: "failed to set read deadline",
+			args: args{
+				config:     Config{},
+				chain:      makeChain(newMockReporter(t)),
+				wsPreSteps: noWsPreSteps,
+				wsConn: newMockWebsocketConn().WithReadDlError(
+					fmt.Errorf("failed to set read deadline")),
+			},
+			assertOk: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			ws := makeWebsocket(tt.args.config, tt.args.chain, tt.args.wsConn)
+			tt.args.wsPreSteps(ws)
+
+			ws.Expect()
+
+			if tt.assertOk {
+				ws.chain.assertOK(t)
+			} else {
+				ws.chain.assertFailed(t)
+			}
+		})
+	}
+}
+
+func TestWebsocketClose(t *testing.T) {
+
+	failedChain := makeChain(newMockReporter(t))
+	failedChain.fail("some previous fail...")
+
+	type args struct {
+		config     Config
+		chain      chain
+		wsConn     WebsocketConn
+		wsPreSteps func(*Websocket)
+		closeCode  []int
+	}
+	tests := []struct {
+		name     string
+		args     args
+		assertOk bool
+	}{
+		{
+			name: "success",
+			args: args{
+				config:     Config{},
+				chain:      makeChain(newMockReporter(t)),
+				wsPreSteps: noWsPreSteps,
+				wsConn:     newMockWebsocketConn(),
+				closeCode:  []int{websocket.CloseNormalClosure},
+			},
+			assertOk: true,
+		},
+		{
+			name: "websocket unusable",
+			args: args{
+				config: Config{},
+				chain:  makeChain(newMockReporter(t)),
+				wsPreSteps: func(ws *Websocket) {
+					ws.Disconnect()
+				},
+				wsConn:    newMockWebsocketConn(),
+				closeCode: []int{websocket.CloseNormalClosure},
+			},
+			assertOk: false,
+		},
+		{
+			name: "too many close codes",
+			args: args{
+				config:     Config{},
+				chain:      makeChain(newMockReporter(t)),
+				wsPreSteps: noWsPreSteps,
+				wsConn:     newMockWebsocketConn(),
+				closeCode:  []int{websocket.CloseNormalClosure, websocket.CloseAbnormalClosure},
+			},
+			assertOk: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			ws := makeWebsocket(tt.args.config, tt.args.chain, tt.args.wsConn)
+
+			tt.args.wsPreSteps(ws)
+
+			ws.Close(tt.args.closeCode...)
+
+			if tt.assertOk {
+				ws.chain.assertOK(t)
+			} else {
+				ws.chain.assertFailed(t)
+			}
+		})
+	}
+}
+
+func TestWebsocketCloseWithBytes(t *testing.T) {
+
+	failedChain := makeChain(newMockReporter(t))
+	failedChain.fail("some previous fail...")
+
+	type args struct {
+		config     Config
+		chain      chain
+		wsConn     WebsocketConn
+		wsPreSteps func(*Websocket)
+		content    []byte
+		closeCode  []int
+	}
+	tests := []struct {
+		name     string
+		args     args
+		assertOk bool
+	}{
+		{
+			name: "success",
+			args: args{
+				config:     Config{},
+				chain:      makeChain(newMockReporter(t)),
+				wsPreSteps: noWsPreSteps,
+				wsConn:     newMockWebsocketConn(),
+				content:    []byte("connection closed..."),
+				closeCode:  []int{websocket.CloseNormalClosure},
+			},
+			assertOk: true,
+		},
+		{
+			name: "websocket unusable",
+			args: args{
+				config: Config{},
+				chain:  makeChain(newMockReporter(t)),
+				wsPreSteps: func(ws *Websocket) {
+					ws.Disconnect()
+				},
+				wsConn:    newMockWebsocketConn(),
+				content:   []byte("connection closed..."),
+				closeCode: []int{websocket.CloseNormalClosure},
+			},
+			assertOk: false,
+		},
+		{
+			name: "too many close codes",
+			args: args{
+				config:     Config{},
+				chain:      makeChain(newMockReporter(t)),
+				wsPreSteps: noWsPreSteps,
+				wsConn:     newMockWebsocketConn(),
+				content:    []byte("connection closed..."),
+				closeCode:  []int{websocket.CloseNormalClosure, websocket.CloseAbnormalClosure},
+			},
+			assertOk: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			ws := makeWebsocket(tt.args.config, tt.args.chain, tt.args.wsConn)
+
+			tt.args.wsPreSteps(ws)
+
+			ws.CloseWithBytes(tt.args.content, tt.args.closeCode...)
+
+			if tt.assertOk {
+				ws.chain.assertOK(t)
+			} else {
+				ws.chain.assertFailed(t)
+			}
+		})
+	}
+}
+
+func TestWebsocketCloseWithText(t *testing.T) {
+
+	failedChain := makeChain(newMockReporter(t))
+	failedChain.fail("some previous fail...")
+
+	type args struct {
+		config     Config
+		chain      chain
+		wsConn     WebsocketConn
+		wsPreSteps func(*Websocket)
+		content    string
+		closeCode  []int
+	}
+	tests := []struct {
+		name     string
+		args     args
+		assertOk bool
+	}{
+		{
+			name: "success",
+			args: args{
+				config:     Config{},
+				chain:      makeChain(newMockReporter(t)),
+				wsPreSteps: noWsPreSteps,
+				wsConn:     newMockWebsocketConn(),
+				content:    "connection closed...",
+				closeCode:  []int{websocket.CloseNormalClosure},
+			},
+			assertOk: true,
+		},
+		{
+			name: "websocket unusable",
+			args: args{
+				config: Config{},
+				chain:  makeChain(newMockReporter(t)),
+				wsPreSteps: func(ws *Websocket) {
+					ws.Disconnect()
+				},
+				wsConn:    newMockWebsocketConn(),
+				content:   "connection closed...",
+				closeCode: []int{websocket.CloseNormalClosure},
+			},
+			assertOk: false,
+		},
+		{
+			name: "too many close codes",
+			args: args{
+				config:     Config{},
+				chain:      makeChain(newMockReporter(t)),
+				wsPreSteps: noWsPreSteps,
+				wsConn:     newMockWebsocketConn(),
+				content:    "connection closed...",
+				closeCode:  []int{websocket.CloseNormalClosure, websocket.CloseAbnormalClosure},
+			},
+			assertOk: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			ws := makeWebsocket(tt.args.config, tt.args.chain, tt.args.wsConn)
+
+			tt.args.wsPreSteps(ws)
+
+			ws.CloseWithText(tt.args.content, tt.args.closeCode...)
+
+			if tt.assertOk {
+				ws.chain.assertOK(t)
+			} else {
+				ws.chain.assertFailed(t)
+			}
+		})
+	}
+}
+
+func TestWebsocketCloseWithJSON(t *testing.T) {
+
+	failedChain := makeChain(newMockReporter(t))
+	failedChain.fail("some previous fail...")
+
+	type args struct {
+		config     Config
+		chain      chain
+		wsConn     WebsocketConn
+		wsPreSteps func(*Websocket)
+		content    interface{}
+		closeCode  []int
+	}
+	tests := []struct {
+		name     string
+		args     args
+		assertOk bool
+	}{
+		{
+			name: "success",
+			args: args{
+				config:     Config{},
+				chain:      makeChain(newMockReporter(t)),
+				wsPreSteps: noWsPreSteps,
+				wsConn:     newMockWebsocketConn(),
+				content: map[string]string{
+					"msg": "connection closing...",
+				},
+				closeCode: []int{websocket.CloseNormalClosure},
+			},
+			assertOk: true,
+		},
+		{
+			name: "websocket unusable",
+			args: args{
+				config: Config{},
+				chain:  makeChain(newMockReporter(t)),
+				wsPreSteps: func(ws *Websocket) {
+					ws.Disconnect()
+				},
+				wsConn: newMockWebsocketConn(),
+				content: map[string]string{
+					"msg": "connection closing...",
+				},
+				closeCode: []int{websocket.CloseNormalClosure},
+			},
+			assertOk: false,
+		},
+		{
+			name: "too many close codes",
+			args: args{
+				config:     Config{},
+				chain:      makeChain(newMockReporter(t)),
+				wsPreSteps: noWsPreSteps,
+				wsConn:     newMockWebsocketConn(),
+				content: map[string]string{
+					"msg": "connection closing...",
+				},
+				closeCode: []int{websocket.CloseNormalClosure, websocket.CloseAbnormalClosure},
+			},
+			assertOk: false,
+		},
+		{
+			name: "marshall failed",
+			args: args{
+				config:     Config{},
+				chain:      makeChain(newMockReporter(t)),
+				wsPreSteps: noWsPreSteps,
+				wsConn:     newMockWebsocketConn(),
+				content:    make(chan int),
+				closeCode:  []int{websocket.CloseNormalClosure},
+			},
+			assertOk: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			ws := makeWebsocket(tt.args.config, tt.args.chain, tt.args.wsConn)
+
+			tt.args.wsPreSteps(ws)
+
+			ws.CloseWithJSON(tt.args.content, tt.args.closeCode...)
+
+			if tt.assertOk {
+				ws.chain.assertOK(t)
+			} else {
+				ws.chain.assertFailed(t)
+			}
+		})
+	}
+}
+
+func TestWebsocketWriteMessage(t *testing.T) {
+	type args struct {
+		config     Config
+		chain      chain
+		wsConn     WebsocketConn
+		wsPreSteps func(*Websocket)
+		typ        int
+		content    []byte
+		closeCode  []int
+	}
+	tests := []struct {
+		name     string
+		args     args
+		assertOk bool
+	}{
+		{
+			name: "text message success",
+			args: args{
+				config:     Config{},
+				chain:      makeChain(newMockReporter(t)),
+				wsPreSteps: noWsPreSteps,
+				wsConn:     newMockWebsocketConn(),
+				typ:        websocket.TextMessage,
+				content:    []byte("random message..."),
+				closeCode:  []int{},
+			},
+			assertOk: true,
+		},
+		{
+			name: "text message fail unusable",
+			args: args{
+				config: Config{},
+				chain:  makeChain(newMockReporter(t)),
+				wsConn: newMockWebsocketConn(),
+				wsPreSteps: func(ws *Websocket) {
+					ws.Disconnect()
+				},
+				typ:       websocket.TextMessage,
+				content:   []byte("random message..."),
+				closeCode: []int{},
+			},
+			assertOk: false,
+		},
+		{
+			name: "text message failed to set write deadline",
+			args: args{
+				config: Config{},
+				chain:  makeChain(newMockReporter(t)),
+				wsConn: newMockWebsocketConn().WithWriteDlError(
+					fmt.Errorf("failed to set write deadline")),
+				wsPreSteps: noWsPreSteps,
+				typ:        websocket.TextMessage,
+				content:    []byte("random message..."),
+				closeCode:  []int{},
+			},
+			assertOk: false,
+		},
+		{
+			name: "text message failed to write to conn",
+			args: args{
+				config: Config{},
+				chain:  makeChain(newMockReporter(t)),
+				wsConn: newMockWebsocketConn().WithWriteMsgError(
+					fmt.Errorf("failed to write message to conn")),
+				wsPreSteps: noWsPreSteps,
+				typ:        websocket.TextMessage,
+				content:    []byte("random message..."),
+				closeCode:  []int{},
+			},
+			assertOk: false,
+		},
+		{
+			name: "text binary message success",
+			args: args{
+				config:     Config{},
+				chain:      makeChain(newMockReporter(t)),
+				wsPreSteps: noWsPreSteps,
+				wsConn:     newMockWebsocketConn(),
+				typ:        websocket.BinaryMessage,
+				content:    []byte("random message..."),
+				closeCode:  []int{},
+			},
+			assertOk: true,
+		},
+		{
+			name: "close message success",
+			args: args{
+				config:     Config{},
+				chain:      makeChain(newMockReporter(t)),
+				wsConn:     newMockWebsocketConn(),
+				wsPreSteps: noWsPreSteps,
+				typ:        websocket.CloseMessage,
+				content:    []byte("closing message..."),
+				closeCode:  []int{websocket.CloseNormalClosure},
+			},
+			assertOk: true,
+		},
+		{
+			name: "close message too many close codes",
+			args: args{
+				config:     Config{},
+				chain:      makeChain(newMockReporter(t)),
+				wsConn:     newMockWebsocketConn(),
+				wsPreSteps: noWsPreSteps,
+				typ:        websocket.CloseMessage,
+				content:    []byte("closing message..."),
+				closeCode:  []int{websocket.CloseNormalClosure, websocket.CloseAbnormalClosure},
+			},
+			assertOk: false,
+		},
+		{
+			name: "unsupported message type",
+			args: args{
+				config:     Config{},
+				chain:      makeChain(newMockReporter(t)),
+				wsConn:     newMockWebsocketConn(),
+				wsPreSteps: noWsPreSteps,
+				typ:        websocket.CloseMandatoryExtension,
+				content:    []byte("unsupported message..."),
+				closeCode:  []int{},
+			},
+			assertOk: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			ws := makeWebsocket(tt.args.config, tt.args.chain, tt.args.wsConn)
+
+			tt.args.wsPreSteps(ws)
+
+			ws.WriteMessage(tt.args.typ, tt.args.content, tt.args.closeCode...)
+
+			if tt.assertOk {
+				ws.chain.assertOK(t)
+			} else {
+				ws.chain.assertFailed(t)
+			}
+		})
+	}
+}
+
+func TestWebsocketWriteBytesBinary(t *testing.T) {
+	type args struct {
+		config     Config
+		chain      chain
+		wsConn     WebsocketConn
+		wsPreSteps func(*Websocket)
+		content    []byte
+	}
+	tests := []struct {
+		name     string
+		args     args
+		assertOk bool
+	}{
+		{
+			name: "success",
+			args: args{
+				config:     Config{},
+				chain:      makeChain(newMockReporter(t)),
+				wsPreSteps: noWsPreSteps,
+				wsConn:     newMockWebsocketConn(),
+				content:    []byte("random message..."),
+			},
+			assertOk: true,
+		},
+		{
+			name: "websocket unusable",
+			args: args{
+				config: Config{},
+				chain:  makeChain(newMockReporter(t)),
+				wsConn: newMockWebsocketConn(),
+				wsPreSteps: func(ws *Websocket) {
+					ws.Disconnect()
+				},
+				content: []byte("random message..."),
+			},
+			assertOk: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			ws := makeWebsocket(tt.args.config, tt.args.chain, tt.args.wsConn)
+
+			tt.args.wsPreSteps(ws)
+
+			ws.WriteBytesBinary(tt.args.content)
+
+			if tt.assertOk {
+				ws.chain.assertOK(t)
+			} else {
+				ws.chain.assertFailed(t)
+			}
+		})
+	}
+}
+
+func TestWebsocketWriteBytesText(t *testing.T) {
+	type args struct {
+		config     Config
+		chain      chain
+		wsConn     WebsocketConn
+		wsPreSteps func(*Websocket)
+		content    []byte
+	}
+	tests := []struct {
+		name     string
+		args     args
+		assertOk bool
+	}{
+		{
+			name: "success",
+			args: args{
+				config:     Config{},
+				chain:      makeChain(newMockReporter(t)),
+				wsPreSteps: noWsPreSteps,
+				wsConn:     newMockWebsocketConn(),
+				content:    []byte("random message..."),
+			},
+			assertOk: true,
+		},
+		{
+			name: "websocket unusable",
+			args: args{
+				config: Config{},
+				chain:  makeChain(newMockReporter(t)),
+				wsConn: newMockWebsocketConn(),
+				wsPreSteps: func(ws *Websocket) {
+					ws.Disconnect()
+				},
+				content: []byte("random message..."),
+			},
+			assertOk: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			ws := makeWebsocket(tt.args.config, tt.args.chain, tt.args.wsConn)
+
+			tt.args.wsPreSteps(ws)
+
+			ws.WriteBytesText(tt.args.content)
+
+			if tt.assertOk {
+				ws.chain.assertOK(t)
+			} else {
+				ws.chain.assertFailed(t)
+			}
+		})
+	}
+}
+
+func TestWebsocketWriteText(t *testing.T) {
+	type args struct {
+		config     Config
+		chain      chain
+		wsConn     WebsocketConn
+		wsPreSteps func(*Websocket)
+		content    string
+	}
+	tests := []struct {
+		name     string
+		args     args
+		assertOk bool
+	}{
+		{
+			name: "success",
+			args: args{
+				config:     Config{},
+				chain:      makeChain(newMockReporter(t)),
+				wsPreSteps: noWsPreSteps,
+				wsConn:     newMockWebsocketConn(),
+				content:    "random message...",
+			},
+			assertOk: true,
+		},
+		{
+			name: "websocket unusable",
+			args: args{
+				config: Config{},
+				chain:  makeChain(newMockReporter(t)),
+				wsConn: newMockWebsocketConn(),
+				wsPreSteps: func(ws *Websocket) {
+					ws.Disconnect()
+				},
+				content: "random message...",
+			},
+			assertOk: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			ws := makeWebsocket(tt.args.config, tt.args.chain, tt.args.wsConn)
+
+			tt.args.wsPreSteps(ws)
+
+			ws.WriteText(tt.args.content)
+
+			if tt.assertOk {
+				ws.chain.assertOK(t)
+			} else {
+				ws.chain.assertFailed(t)
+			}
+		})
+	}
+}
+
+func TestWebsocketWriteJSON(t *testing.T) {
+	type args struct {
+		config     Config
+		chain      chain
+		wsConn     WebsocketConn
+		wsPreSteps func(*Websocket)
+		content    interface{}
+	}
+	tests := []struct {
+		name     string
+		args     args
+		assertOk bool
+	}{
+		{
+			name: "success",
+			args: args{
+				config:     Config{},
+				chain:      makeChain(newMockReporter(t)),
+				wsPreSteps: noWsPreSteps,
+				wsConn:     newMockWebsocketConn(),
+				content: map[string]string{
+					"msg": "random message",
+				},
+			},
+			assertOk: true,
+		},
+		{
+			name: "websocket unusable",
+			args: args{
+				config: Config{},
+				chain:  makeChain(newMockReporter(t)),
+				wsConn: newMockWebsocketConn(),
+				wsPreSteps: func(ws *Websocket) {
+					ws.Disconnect()
+				},
+				content: map[string]string{
+					"msg": "random message",
+				},
+			},
+			assertOk: false,
+		},
+		{
+			name: "JSON marshal failed",
+			args: args{
+				config:     Config{},
+				chain:      makeChain(newMockReporter(t)),
+				wsConn:     newMockWebsocketConn(),
+				wsPreSteps: noWsPreSteps,
+				content:    make(chan int),
+			},
+			assertOk: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			ws := makeWebsocket(tt.args.config, tt.args.chain, tt.args.wsConn)
+
+			tt.args.wsPreSteps(ws)
+
+			ws.WriteJSON(tt.args.content)
+
+			if tt.assertOk {
+				ws.chain.assertOK(t)
+			} else {
+				ws.chain.assertFailed(t)
+			}
+		})
+	}
+}
+
+func TestWebsocket_Subprotocol(t *testing.T) {
+	subproto := "soap"
+	ws := NewWebsocket(Config{}, newMockWebsocketConn().WithSubprotocol(subproto))
+
+	ws.Subprotocol()
+
+	if got := ws.Subprotocol().value; got != subproto {
+		t.Errorf("Websocket.Subprotocol() = %v, want %v", got, subproto)
+	}
+}
+
+func TestWebsocket_setReadDeadline(t *testing.T) {
+	type args struct {
+		config Config
+		chain  chain
+		wsConn WebsocketConn
+	}
+	tests := []struct {
+		name     string
+		args     args
+		assertOk bool
+	}{
+		{
+			name: "success",
+			args: args{
+				config: Config{},
+				chain:  makeChain(newMockReporter(t)),
+				wsConn: newMockWebsocketConn(),
+			},
+			assertOk: true,
+		},
+		{
+			name: "conn.SetReadDeadline error",
+			args: args{
+				config: Config{},
+				chain:  makeChain(newMockReporter(t)),
+				wsConn: newMockWebsocketConn().WithReadDlError(
+					fmt.Errorf("Failed to set read deadline")),
+			},
+			assertOk: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			ws := makeWebsocket(tt.args.config, tt.args.chain, tt.args.wsConn).
+				WithReadTimeout(time.Second)
+
+			ws.setReadDeadline()
+
+			if tt.assertOk {
+				ws.chain.assertOK(t)
+			} else {
+				ws.chain.assertFailed(t)
+			}
+		})
+	}
+}
+
+func TestWebsocket_setWriteDeadline(t *testing.T) {
+	type args struct {
+		config Config
+		chain  chain
+		wsConn WebsocketConn
+	}
+	tests := []struct {
+		name     string
+		args     args
+		assertOk bool
+	}{
+		{
+			name: "success",
+			args: args{
+				config: Config{},
+				chain:  makeChain(newMockReporter(t)),
+				wsConn: newMockWebsocketConn(),
+			},
+			assertOk: true,
+		},
+		{
+			name: "conn.SetReadDeadline error",
+			args: args{
+				config: Config{},
+				chain:  makeChain(newMockReporter(t)),
+				wsConn: newMockWebsocketConn().WithWriteDlError(
+					fmt.Errorf("Failed to set read deadline")),
+			},
+			assertOk: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			ws := makeWebsocket(tt.args.config, tt.args.chain, tt.args.wsConn).
+				WithWriteTimeout(time.Second)
+
+			ws.setWriteDeadline()
+
+			if tt.assertOk {
+				ws.chain.assertOK(t)
+			} else {
+				ws.chain.assertFailed(t)
+			}
+		})
+	}
+}
+
+func TestWebsocket_Disconnect(t *testing.T) {
+	type args struct {
+		config Config
+		chain  chain
+		wsConn WebsocketConn
+	}
+	tests := []struct {
+		name     string
+		args     args
+		assertOk bool
+	}{
+		{
+			name: "success",
+			args: args{
+				config: Config{},
+				chain:  makeChain(newMockReporter(t)),
+				wsConn: newMockWebsocketConn(),
+			},
+			assertOk: true,
+		},
+		{
+			name: "conn close failed",
+			args: args{
+				config: Config{},
+				chain:  makeChain(newMockReporter(t)),
+				wsConn: newMockWebsocketConn().WithCloseError(
+					fmt.Errorf("failed to close ws conn")),
+			},
+			assertOk: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			ws := makeWebsocket(tt.args.config, tt.args.chain, tt.args.wsConn)
+
+			ws.Disconnect()
+
+			if tt.assertOk {
+				ws.chain.assertOK(t)
+			} else {
+				ws.chain.assertFailed(t)
+			}
+		})
+	}
+}
+
+func TestPrintRead(t *testing.T) {
+	printer := newMockWsPrinter()
 	config := Config{
-		Reporter: newMockReporter(t),
+		Printers: []Printer{printer},
 	}
+	ws := makeWebsocket(config, makeChain(newMockReporter(t)), newMockWebsocketConn())
 
-	ws := NewWebsocket(config, nil)
+	ws.printRead(websocket.CloseMessage,
+		[]byte("random message"),
+		websocket.CloseNormalClosure)
 
-	msg := ws.Expect()
-	msg.chain.assertFailed(t)
+	if !printer.isReadFrom {
+		t.Errorf("Websocket.printRead() failed to read from printer")
+	}
+}
 
-	ws.chain.assertFailed(t)
+func TestPrintWrite(t *testing.T) {
+	printer := newMockWsPrinter()
+	config := Config{
+		Printers: []Printer{printer},
+	}
+	ws := makeWebsocket(config, makeChain(newMockReporter(t)), newMockWebsocketConn())
+
+	ws.printWrite(websocket.CloseMessage,
+		[]byte("random message"),
+		websocket.CloseNormalClosure)
+
+	if !printer.isWrittenTo {
+		t.Errorf("Websocket.printWrite() failed to write to printer")
+	}
 }