Browse Source

Add some test cases and run test cases on binding/render dir (#1168)

* Travis run test cases on binding and render dir and add some test cases for binding and render
田欧 7 years ago
parent
commit
783c7ee9c1
5 changed files with 957 additions and 2 deletions
  1. 1 0
      .gitignore
  2. 1 1
      Makefile
  3. 768 0
      binding/binding_test.go
  4. 13 0
      coverage.sh
  5. 174 1
      render/render_test.go

+ 1 - 0
.gitignore

@@ -2,3 +2,4 @@ vendor/*
 !vendor/vendor.json
 coverage.out
 count.out
+test

+ 1 - 1
Makefile

@@ -9,7 +9,7 @@ install: deps
 
 .PHONY: test
 test:
-	go test -v -covermode=count -coverprofile=coverage.out
+	sh coverage.sh
 
 .PHONY: fmt
 fmt:

+ 768 - 0
binding/binding_test.go

@@ -6,9 +6,13 @@ package binding
 
 import (
 	"bytes"
+	"encoding/json"
+	"errors"
+	"io/ioutil"
 	"mime/multipart"
 	"net/http"
 	"testing"
+	"time"
 
 	"github.com/gin-gonic/gin/binding/example"
 	"github.com/golang/protobuf/proto"
@@ -25,6 +29,116 @@ type FooBarStruct struct {
 	Bar string `msgpack:"bar" json:"bar" form:"bar" xml:"bar" binding:"required"`
 }
 
+type FooStructUseNumber struct {
+	Foo interface{} `json:"foo" binding:"required"`
+}
+
+type FooBarStructForTimeType struct {
+	TimeFoo time.Time `form:"time_foo" time_format:"2006-01-02" time_utc:"1" time_location:"Asia/Chongqing"`
+	TimeBar time.Time `form:"time_bar" time_format:"2006-01-02" time_utc:"1"`
+}
+
+type FooStructForTimeTypeNotFormat struct {
+	TimeFoo time.Time `form:"time_foo"`
+}
+
+type FooStructForTimeTypeFailFormat struct {
+	TimeFoo time.Time `form:"time_foo" time_format:"2017-11-15"`
+}
+
+type FooStructForTimeTypeFailLocation struct {
+	TimeFoo time.Time `form:"time_foo" time_format:"2006-01-02" time_location:"/asia/chongqing"`
+}
+
+type FooStructForMapType struct {
+	// Unknown type: not support map
+	MapFoo map[string]interface{} `form:"map_foo"`
+}
+
+type InvalidNameType struct {
+	TestName string `invalid_name:"test_name"`
+}
+
+type InvalidNameMapType struct {
+	TestName struct {
+		MapFoo map[string]interface{} `form:"map_foo"`
+	}
+}
+
+type FooStructForSliceType struct {
+	SliceFoo []int `form:"slice_foo"`
+}
+
+type FooStructForSliceMapType struct {
+	// Unknown type: not support map
+	SliceMapFoo []map[string]interface{} `form:"slice_map_foo"`
+}
+
+type FooBarStructForIntType struct {
+	IntFoo int `form:"int_foo"`
+	IntBar int `form:"int_bar" binding:"required"`
+}
+
+type FooBarStructForInt8Type struct {
+	Int8Foo int8 `form:"int8_foo"`
+	Int8Bar int8 `form:"int8_bar" binding:"required"`
+}
+
+type FooBarStructForInt16Type struct {
+	Int16Foo int16 `form:"int16_foo"`
+	Int16Bar int16 `form:"int16_bar" binding:"required"`
+}
+
+type FooBarStructForInt32Type struct {
+	Int32Foo int32 `form:"int32_foo"`
+	Int32Bar int32 `form:"int32_bar" binding:"required"`
+}
+
+type FooBarStructForInt64Type struct {
+	Int64Foo int64 `form:"int64_foo"`
+	Int64Bar int64 `form:"int64_bar" binding:"required"`
+}
+
+type FooBarStructForUintType struct {
+	UintFoo uint `form:"uint_foo"`
+	UintBar uint `form:"uint_bar" binding:"required"`
+}
+
+type FooBarStructForUint8Type struct {
+	Uint8Foo uint8 `form:"uint8_foo"`
+	Uint8Bar uint8 `form:"uint8_bar" binding:"required"`
+}
+
+type FooBarStructForUint16Type struct {
+	Uint16Foo uint16 `form:"uint16_foo"`
+	Uint16Bar uint16 `form:"uint16_bar" binding:"required"`
+}
+
+type FooBarStructForUint32Type struct {
+	Uint32Foo uint32 `form:"uint32_foo"`
+	Uint32Bar uint32 `form:"uint32_bar" binding:"required"`
+}
+
+type FooBarStructForUint64Type struct {
+	Uint64Foo uint64 `form:"uint64_foo"`
+	Uint64Bar uint64 `form:"uint64_bar" binding:"required"`
+}
+
+type FooBarStructForBoolType struct {
+	BoolFoo bool `form:"bool_foo"`
+	BoolBar bool `form:"bool_bar" binding:"required"`
+}
+
+type FooBarStructForFloat32Type struct {
+	Float32Foo float32 `form:"float32_foo"`
+	Float32Bar float32 `form:"float32_bar" binding:"required"`
+}
+
+type FooBarStructForFloat64Type struct {
+	Float64Foo float64 `form:"float64_foo"`
+	Float64Bar float64 `form:"float64_bar" binding:"required"`
+}
+
 func TestBindingDefault(t *testing.T) {
 	assert.Equal(t, Default("GET", ""), Form)
 	assert.Equal(t, Default("GET", MIMEJSON), Form)
@@ -55,6 +169,20 @@ func TestBindingJSON(t *testing.T) {
 		`{"foo": "bar"}`, `{"bar": "foo"}`)
 }
 
+func TestBindingJSONUseNumber(t *testing.T) {
+	testBodyBindingUseNumber(t,
+		JSON, "json",
+		"/", "/",
+		`{"foo": 123}`, `{"bar": "foo"}`)
+}
+
+func TestBindingJSONUseNumber2(t *testing.T) {
+	testBodyBindingUseNumber2(t,
+		JSON, "json",
+		"/", "/",
+		`{"foo": 123}`, `{"bar": "foo"}`)
+}
+
 func TestBindingForm(t *testing.T) {
 	testFormBinding(t, "POST",
 		"/", "/",
@@ -67,6 +195,174 @@ func TestBindingForm2(t *testing.T) {
 		"", "")
 }
 
+func TestBindingFormForTime(t *testing.T) {
+	testFormBindingForTime(t, "POST",
+		"/", "/",
+		"time_foo=2017-11-15&time_bar=", "bar2=foo")
+	testFormBindingForTimeNotFormat(t, "POST",
+		"/", "/",
+		"time_foo=2017-11-15", "bar2=foo")
+	testFormBindingForTimeFailFormat(t, "POST",
+		"/", "/",
+		"time_foo=2017-11-15", "bar2=foo")
+	testFormBindingForTimeFailLocation(t, "POST",
+		"/", "/",
+		"time_foo=2017-11-15", "bar2=foo")
+}
+
+func TestBindingFormForTime2(t *testing.T) {
+	testFormBindingForTime(t, "GET",
+		"/?time_foo=2017-11-15&time_bar=", "/?bar2=foo",
+		"", "")
+	testFormBindingForTimeNotFormat(t, "GET",
+		"/?time_foo=2017-11-15", "/?bar2=foo",
+		"", "")
+	testFormBindingForTimeFailFormat(t, "GET",
+		"/?time_foo=2017-11-15", "/?bar2=foo",
+		"", "")
+	testFormBindingForTimeFailLocation(t, "GET",
+		"/?time_foo=2017-11-15", "/?bar2=foo",
+		"", "")
+}
+
+func TestBindingFormInvalidName(t *testing.T) {
+	testFormBindingInvalidName(t, "POST",
+		"/", "/",
+		"test_name=bar", "bar2=foo")
+}
+
+func TestBindingFormInvalidName2(t *testing.T) {
+	testFormBindingInvalidName2(t, "POST",
+		"/", "/",
+		"map_foo=bar", "bar2=foo")
+}
+
+func TestBindingFormForType(t *testing.T) {
+	testFormBindingForType(t, "POST",
+		"/", "/",
+		"map_foo=", "bar2=1", "Map")
+
+	testFormBindingForType(t, "POST",
+		"/", "/",
+		"slice_foo=1&slice_foo=2", "bar2=1&bar2=2", "Slice")
+
+	testFormBindingForType(t, "GET",
+		"/?slice_foo=1&slice_foo=2", "/?bar2=1&bar2=2",
+		"", "", "Slice")
+
+	testFormBindingForType(t, "POST",
+		"/", "/",
+		"slice_map_foo=1&slice_map_foo=2", "bar2=1&bar2=2", "SliceMap")
+
+	testFormBindingForType(t, "GET",
+		"/?slice_map_foo=1&slice_map_foo=2", "/?bar2=1&bar2=2",
+		"", "", "SliceMap")
+
+	testFormBindingForType(t, "POST",
+		"/", "/",
+		"int_foo=&int_bar=-12", "bar2=-123", "Int")
+
+	testFormBindingForType(t, "GET",
+		"/?int_foo=&int_bar=-12", "/?bar2=-123",
+		"", "", "Int")
+
+	testFormBindingForType(t, "POST",
+		"/", "/",
+		"int8_foo=&int8_bar=-12", "bar2=-123", "Int8")
+
+	testFormBindingForType(t, "GET",
+		"/?int8_foo=&int8_bar=-12", "/?bar2=-123",
+		"", "", "Int8")
+
+	testFormBindingForType(t, "POST",
+		"/", "/",
+		"int16_foo=&int16_bar=-12", "bar2=-123", "Int16")
+
+	testFormBindingForType(t, "GET",
+		"/?int16_foo=&int16_bar=-12", "/?bar2=-123",
+		"", "", "Int16")
+
+	testFormBindingForType(t, "POST",
+		"/", "/",
+		"int32_foo=&int32_bar=-12", "bar2=-123", "Int32")
+
+	testFormBindingForType(t, "GET",
+		"/?int32_foo=&int32_bar=-12", "/?bar2=-123",
+		"", "", "Int32")
+
+	testFormBindingForType(t, "POST",
+		"/", "/",
+		"int64_foo=&int64_bar=-12", "bar2=-123", "Int64")
+
+	testFormBindingForType(t, "GET",
+		"/?int64_foo=&int64_bar=-12", "/?bar2=-123",
+		"", "", "Int64")
+
+	testFormBindingForType(t, "POST",
+		"/", "/",
+		"uint_foo=&uint_bar=12", "bar2=123", "Uint")
+
+	testFormBindingForType(t, "GET",
+		"/?uint_foo=&uint_bar=12", "/?bar2=123",
+		"", "", "Uint")
+
+	testFormBindingForType(t, "POST",
+		"/", "/",
+		"uint8_foo=&uint8_bar=12", "bar2=123", "Uint8")
+
+	testFormBindingForType(t, "GET",
+		"/?uint8_foo=&uint8_bar=12", "/?bar2=123",
+		"", "", "Uint8")
+
+	testFormBindingForType(t, "POST",
+		"/", "/",
+		"uint16_foo=&uint16_bar=12", "bar2=123", "Uint16")
+
+	testFormBindingForType(t, "GET",
+		"/?uint16_foo=&uint16_bar=12", "/?bar2=123",
+		"", "", "Uint16")
+
+	testFormBindingForType(t, "POST",
+		"/", "/",
+		"uint32_foo=&uint32_bar=12", "bar2=123", "Uint32")
+
+	testFormBindingForType(t, "GET",
+		"/?uint32_foo=&uint32_bar=12", "/?bar2=123",
+		"", "", "Uint32")
+
+	testFormBindingForType(t, "POST",
+		"/", "/",
+		"uint64_foo=&uint64_bar=12", "bar2=123", "Uint64")
+
+	testFormBindingForType(t, "GET",
+		"/?uint64_foo=&uint64_bar=12", "/?bar2=123",
+		"", "", "Uint64")
+
+	testFormBindingForType(t, "POST",
+		"/", "/",
+		"bool_foo=&bool_bar=true", "bar2=true", "Bool")
+
+	testFormBindingForType(t, "GET",
+		"/?bool_foo=&bool_bar=true", "/?bar2=true",
+		"", "", "Bool")
+
+	testFormBindingForType(t, "POST",
+		"/", "/",
+		"float32_foo=&float32_bar=-12.34", "bar2=12.3", "Float32")
+
+	testFormBindingForType(t, "GET",
+		"/?float32_foo=&float32_bar=-12.34", "/?bar2=12.3",
+		"", "", "Float32")
+
+	testFormBindingForType(t, "POST",
+		"/", "/",
+		"float64_foo=&float64_bar=-12.34", "bar2=12.3", "Float64")
+
+	testFormBindingForType(t, "GET",
+		"/?float64_foo=&float64_bar=-12.34", "/?bar2=12.3",
+		"", "", "Float64")
+}
+
 func TestBindingQuery(t *testing.T) {
 	testQueryBinding(t, "POST",
 		"/?foo=bar&bar=foo", "/",
@@ -79,6 +375,18 @@ func TestBindingQuery2(t *testing.T) {
 		"foo=unused", "")
 }
 
+func TestBindingQueryFail(t *testing.T) {
+	testQueryBindingFail(t, "POST",
+		"/?map_foo=", "/",
+		"map_foo=unused", "bar2=foo")
+}
+
+func TestBindingQueryFail2(t *testing.T) {
+	testQueryBindingFail(t, "GET",
+		"/?map_foo=", "/?bar2=foo",
+		"map_foo=unused", "")
+}
+
 func TestBindingXML(t *testing.T) {
 	testBodyBinding(t,
 		XML, "xml",
@@ -86,12 +394,25 @@ func TestBindingXML(t *testing.T) {
 		"<map><foo>bar</foo></map>", "<map><bar>foo</bar></map>")
 }
 
+func TestBindingXMLFail(t *testing.T) {
+	testBodyBindingFail(t,
+		XML, "xml",
+		"/", "/",
+		"<map><foo>bar<foo></map>", "<map><bar>foo</bar></map>")
+}
+
 func createFormPostRequest() *http.Request {
 	req, _ := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", bytes.NewBufferString("foo=bar&bar=foo"))
 	req.Header.Set("Content-Type", MIMEPOSTForm)
 	return req
 }
 
+func createFormPostRequestFail() *http.Request {
+	req, _ := http.NewRequest("POST", "/?map_foo=getfoo", bytes.NewBufferString("map_foo=bar"))
+	req.Header.Set("Content-Type", MIMEPOSTForm)
+	return req
+}
+
 func createFormMultipartRequest() *http.Request {
 	boundary := "--testboundary"
 	body := new(bytes.Buffer)
@@ -106,24 +427,53 @@ func createFormMultipartRequest() *http.Request {
 	return req
 }
 
+func createFormMultipartRequestFail() *http.Request {
+	boundary := "--testboundary"
+	body := new(bytes.Buffer)
+	mw := multipart.NewWriter(body)
+	defer mw.Close()
+
+	mw.SetBoundary(boundary)
+	mw.WriteField("map_foo", "bar")
+	req, _ := http.NewRequest("POST", "/?map_foo=getfoo", body)
+	req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary)
+	return req
+}
+
 func TestBindingFormPost(t *testing.T) {
 	req := createFormPostRequest()
 	var obj FooBarStruct
 	FormPost.Bind(req, &obj)
 
+	assert.Equal(t, FormPost.Name(), "form-urlencoded")
 	assert.Equal(t, obj.Foo, "bar")
 	assert.Equal(t, obj.Bar, "foo")
 }
 
+func TestBindingFormPostFail(t *testing.T) {
+	req := createFormPostRequestFail()
+	var obj FooStructForMapType
+	err := FormPost.Bind(req, &obj)
+	assert.Error(t, err)
+}
+
 func TestBindingFormMultipart(t *testing.T) {
 	req := createFormMultipartRequest()
 	var obj FooBarStruct
 	FormMultipart.Bind(req, &obj)
 
+	assert.Equal(t, FormMultipart.Name(), "multipart/form-data")
 	assert.Equal(t, obj.Foo, "bar")
 	assert.Equal(t, obj.Bar, "foo")
 }
 
+func TestBindingFormMultipartFail(t *testing.T) {
+	req := createFormMultipartRequestFail()
+	var obj FooStructForMapType
+	err := FormMultipart.Bind(req, &obj)
+	assert.Error(t, err)
+}
+
 func TestBindingProtoBuf(t *testing.T) {
 	test := &example.Test{
 		Label: proto.String("yes"),
@@ -136,6 +486,18 @@ func TestBindingProtoBuf(t *testing.T) {
 		string(data), string(data[1:]))
 }
 
+func TestBindingProtoBufFail(t *testing.T) {
+	test := &example.Test{
+		Label: proto.String("yes"),
+	}
+	data, _ := proto.Marshal(test)
+
+	testProtoBodyBindingFail(t,
+		ProtoBuf, "protobuf",
+		"/", "/",
+		string(data), string(data[1:]))
+}
+
 func TestBindingMsgPack(t *testing.T) {
 	test := FooStruct{
 		Foo: "bar",
@@ -216,6 +578,323 @@ func testFormBinding(t *testing.T, method, path, badPath, body, badBody string)
 	assert.Error(t, err)
 }
 
+func TestFormBindingFail(t *testing.T) {
+	b := Form
+	assert.Equal(t, b.Name(), "form")
+
+	obj := FooBarStruct{}
+	req, _ := http.NewRequest("POST", "/", nil)
+	err := b.Bind(req, &obj)
+	assert.Error(t, err)
+}
+
+func TestFormPostBindingFail(t *testing.T) {
+	b := FormPost
+	assert.Equal(t, b.Name(), "form-urlencoded")
+
+	obj := FooBarStruct{}
+	req, _ := http.NewRequest("POST", "/", nil)
+	err := b.Bind(req, &obj)
+	assert.Error(t, err)
+}
+
+func TestFormMultipartBindingFail(t *testing.T) {
+	b := FormMultipart
+	assert.Equal(t, b.Name(), "multipart/form-data")
+
+	obj := FooBarStruct{}
+	req, _ := http.NewRequest("POST", "/", nil)
+	err := b.Bind(req, &obj)
+	assert.Error(t, err)
+}
+
+func testFormBindingForTime(t *testing.T, method, path, badPath, body, badBody string) {
+	b := Form
+	assert.Equal(t, b.Name(), "form")
+
+	obj := FooBarStructForTimeType{}
+	req := requestWithBody(method, path, body)
+	if method == "POST" {
+		req.Header.Add("Content-Type", MIMEPOSTForm)
+	}
+	err := b.Bind(req, &obj)
+
+	assert.NoError(t, err)
+	assert.Equal(t, obj.TimeFoo.Unix(), int64(1510675200))
+	assert.Equal(t, obj.TimeFoo.Location().String(), "Asia/Chongqing")
+	assert.Equal(t, obj.TimeBar.Unix(), int64(-62135596800))
+	assert.Equal(t, obj.TimeBar.Location().String(), "UTC")
+
+	obj = FooBarStructForTimeType{}
+	req = requestWithBody(method, badPath, badBody)
+	err = JSON.Bind(req, &obj)
+	assert.Error(t, err)
+}
+
+func testFormBindingForTimeNotFormat(t *testing.T, method, path, badPath, body, badBody string) {
+	b := Form
+	assert.Equal(t, b.Name(), "form")
+
+	obj := FooStructForTimeTypeNotFormat{}
+	req := requestWithBody(method, path, body)
+	if method == "POST" {
+		req.Header.Add("Content-Type", MIMEPOSTForm)
+	}
+	err := b.Bind(req, &obj)
+	assert.Error(t, err)
+
+	obj = FooStructForTimeTypeNotFormat{}
+	req = requestWithBody(method, badPath, badBody)
+	err = JSON.Bind(req, &obj)
+	assert.Error(t, err)
+}
+
+func testFormBindingForTimeFailFormat(t *testing.T, method, path, badPath, body, badBody string) {
+	b := Form
+	assert.Equal(t, b.Name(), "form")
+
+	obj := FooStructForTimeTypeFailFormat{}
+	req := requestWithBody(method, path, body)
+	if method == "POST" {
+		req.Header.Add("Content-Type", MIMEPOSTForm)
+	}
+	err := b.Bind(req, &obj)
+	assert.Error(t, err)
+
+	obj = FooStructForTimeTypeFailFormat{}
+	req = requestWithBody(method, badPath, badBody)
+	err = JSON.Bind(req, &obj)
+	assert.Error(t, err)
+}
+
+func testFormBindingForTimeFailLocation(t *testing.T, method, path, badPath, body, badBody string) {
+	b := Form
+	assert.Equal(t, b.Name(), "form")
+
+	obj := FooStructForTimeTypeFailLocation{}
+	req := requestWithBody(method, path, body)
+	if method == "POST" {
+		req.Header.Add("Content-Type", MIMEPOSTForm)
+	}
+	err := b.Bind(req, &obj)
+	assert.Error(t, err)
+
+	obj = FooStructForTimeTypeFailLocation{}
+	req = requestWithBody(method, badPath, badBody)
+	err = JSON.Bind(req, &obj)
+	assert.Error(t, err)
+}
+
+func testFormBindingInvalidName(t *testing.T, method, path, badPath, body, badBody string) {
+	b := Form
+	assert.Equal(t, b.Name(), "form")
+
+	obj := InvalidNameType{}
+	req := requestWithBody(method, path, body)
+	if method == "POST" {
+		req.Header.Add("Content-Type", MIMEPOSTForm)
+	}
+	err := b.Bind(req, &obj)
+	assert.NoError(t, err)
+	assert.Equal(t, obj.TestName, "")
+
+	obj = InvalidNameType{}
+	req = requestWithBody(method, badPath, badBody)
+	err = JSON.Bind(req, &obj)
+	assert.Error(t, err)
+}
+
+func testFormBindingInvalidName2(t *testing.T, method, path, badPath, body, badBody string) {
+	b := Form
+	assert.Equal(t, b.Name(), "form")
+
+	obj := InvalidNameMapType{}
+	req := requestWithBody(method, path, body)
+	if method == "POST" {
+		req.Header.Add("Content-Type", MIMEPOSTForm)
+	}
+	err := b.Bind(req, &obj)
+	assert.Error(t, err)
+
+	obj = InvalidNameMapType{}
+	req = requestWithBody(method, badPath, badBody)
+	err = JSON.Bind(req, &obj)
+	assert.Error(t, err)
+}
+
+func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody string, typ string) {
+	b := Form
+	assert.Equal(t, b.Name(), "form")
+
+	req := requestWithBody(method, path, body)
+	if method == "POST" {
+		req.Header.Add("Content-Type", MIMEPOSTForm)
+	}
+	switch typ {
+	case "Int":
+		obj := FooBarStructForIntType{}
+		err := b.Bind(req, &obj)
+		assert.NoError(t, err)
+		assert.Equal(t, obj.IntFoo, int(0))
+		assert.Equal(t, obj.IntBar, int(-12))
+
+		obj = FooBarStructForIntType{}
+		req = requestWithBody(method, badPath, badBody)
+		err = JSON.Bind(req, &obj)
+		assert.Error(t, err)
+	case "Int8":
+		obj := FooBarStructForInt8Type{}
+		err := b.Bind(req, &obj)
+		assert.NoError(t, err)
+		assert.Equal(t, obj.Int8Foo, int8(0))
+		assert.Equal(t, obj.Int8Bar, int8(-12))
+
+		obj = FooBarStructForInt8Type{}
+		req = requestWithBody(method, badPath, badBody)
+		err = JSON.Bind(req, &obj)
+		assert.Error(t, err)
+	case "Int16":
+		obj := FooBarStructForInt16Type{}
+		err := b.Bind(req, &obj)
+		assert.NoError(t, err)
+		assert.Equal(t, obj.Int16Foo, int16(0))
+		assert.Equal(t, obj.Int16Bar, int16(-12))
+
+		obj = FooBarStructForInt16Type{}
+		req = requestWithBody(method, badPath, badBody)
+		err = JSON.Bind(req, &obj)
+		assert.Error(t, err)
+	case "Int32":
+		obj := FooBarStructForInt32Type{}
+		err := b.Bind(req, &obj)
+		assert.NoError(t, err)
+		assert.Equal(t, obj.Int32Foo, int32(0))
+		assert.Equal(t, obj.Int32Bar, int32(-12))
+
+		obj = FooBarStructForInt32Type{}
+		req = requestWithBody(method, badPath, badBody)
+		err = JSON.Bind(req, &obj)
+		assert.Error(t, err)
+	case "Int64":
+		obj := FooBarStructForInt64Type{}
+		err := b.Bind(req, &obj)
+		assert.NoError(t, err)
+		assert.Equal(t, obj.Int64Foo, int64(0))
+		assert.Equal(t, obj.Int64Bar, int64(-12))
+
+		obj = FooBarStructForInt64Type{}
+		req = requestWithBody(method, badPath, badBody)
+		err = JSON.Bind(req, &obj)
+		assert.Error(t, err)
+	case "Uint":
+		obj := FooBarStructForUintType{}
+		err := b.Bind(req, &obj)
+		assert.NoError(t, err)
+		assert.Equal(t, obj.UintFoo, uint(0x0))
+		assert.Equal(t, obj.UintBar, uint(0xc))
+
+		obj = FooBarStructForUintType{}
+		req = requestWithBody(method, badPath, badBody)
+		err = JSON.Bind(req, &obj)
+		assert.Error(t, err)
+	case "Uint8":
+		obj := FooBarStructForUint8Type{}
+		err := b.Bind(req, &obj)
+		assert.NoError(t, err)
+		assert.Equal(t, obj.Uint8Foo, uint8(0x0))
+		assert.Equal(t, obj.Uint8Bar, uint8(0xc))
+
+		obj = FooBarStructForUint8Type{}
+		req = requestWithBody(method, badPath, badBody)
+		err = JSON.Bind(req, &obj)
+		assert.Error(t, err)
+	case "Uint16":
+		obj := FooBarStructForUint16Type{}
+		err := b.Bind(req, &obj)
+		assert.NoError(t, err)
+		assert.Equal(t, obj.Uint16Foo, uint16(0x0))
+		assert.Equal(t, obj.Uint16Bar, uint16(0xc))
+
+		obj = FooBarStructForUint16Type{}
+		req = requestWithBody(method, badPath, badBody)
+		err = JSON.Bind(req, &obj)
+		assert.Error(t, err)
+	case "Uint32":
+		obj := FooBarStructForUint32Type{}
+		err := b.Bind(req, &obj)
+		assert.NoError(t, err)
+		assert.Equal(t, obj.Uint32Foo, uint32(0x0))
+		assert.Equal(t, obj.Uint32Bar, uint32(0xc))
+
+		obj = FooBarStructForUint32Type{}
+		req = requestWithBody(method, badPath, badBody)
+		err = JSON.Bind(req, &obj)
+		assert.Error(t, err)
+	case "Uint64":
+		obj := FooBarStructForUint64Type{}
+		err := b.Bind(req, &obj)
+		assert.NoError(t, err)
+		assert.Equal(t, obj.Uint64Foo, uint64(0x0))
+		assert.Equal(t, obj.Uint64Bar, uint64(0xc))
+
+		obj = FooBarStructForUint64Type{}
+		req = requestWithBody(method, badPath, badBody)
+		err = JSON.Bind(req, &obj)
+		assert.Error(t, err)
+	case "Float32":
+		obj := FooBarStructForFloat32Type{}
+		err := b.Bind(req, &obj)
+		assert.NoError(t, err)
+		assert.Equal(t, obj.Float32Foo, float32(0.0))
+		assert.Equal(t, obj.Float32Bar, float32(-12.34))
+
+		obj = FooBarStructForFloat32Type{}
+		req = requestWithBody(method, badPath, badBody)
+		err = JSON.Bind(req, &obj)
+		assert.Error(t, err)
+	case "Float64":
+		obj := FooBarStructForFloat64Type{}
+		err := b.Bind(req, &obj)
+		assert.NoError(t, err)
+		assert.Equal(t, obj.Float64Foo, float64(0.0))
+		assert.Equal(t, obj.Float64Bar, float64(-12.34))
+
+		obj = FooBarStructForFloat64Type{}
+		req = requestWithBody(method, badPath, badBody)
+		err = JSON.Bind(req, &obj)
+		assert.Error(t, err)
+	case "Bool":
+		obj := FooBarStructForBoolType{}
+		err := b.Bind(req, &obj)
+		assert.NoError(t, err)
+		assert.Equal(t, obj.BoolFoo, false)
+		assert.Equal(t, obj.BoolBar, true)
+
+		obj = FooBarStructForBoolType{}
+		req = requestWithBody(method, badPath, badBody)
+		err = JSON.Bind(req, &obj)
+		assert.Error(t, err)
+	case "Slice":
+		obj := FooStructForSliceType{}
+		err := b.Bind(req, &obj)
+		assert.NoError(t, err)
+		assert.Equal(t, obj.SliceFoo, []int{1, 2})
+
+		obj = FooStructForSliceType{}
+		req = requestWithBody(method, badPath, badBody)
+		err = JSON.Bind(req, &obj)
+		assert.Error(t, err)
+	case "Map":
+		obj := FooStructForMapType{}
+		err := b.Bind(req, &obj)
+		assert.Error(t, err)
+	case "SliceMap":
+		obj := FooStructForSliceMapType{}
+		err := b.Bind(req, &obj)
+		assert.Error(t, err)
+	}
+}
+
 func testQueryBinding(t *testing.T, method, path, badPath, body, badBody string) {
 	b := Query
 	assert.Equal(t, b.Name(), "query")
@@ -231,6 +910,19 @@ func testQueryBinding(t *testing.T, method, path, badPath, body, badBody string)
 	assert.Equal(t, obj.Bar, "foo")
 }
 
+func testQueryBindingFail(t *testing.T, method, path, badPath, body, badBody string) {
+	b := Query
+	assert.Equal(t, b.Name(), "query")
+
+	obj := FooStructForMapType{}
+	req := requestWithBody(method, path, body)
+	if method == "POST" {
+		req.Header.Add("Content-Type", MIMEPOSTForm)
+	}
+	err := b.Bind(req, &obj)
+	assert.Error(t, err)
+}
+
 func testBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody string) {
 	assert.Equal(t, b.Name(), name)
 
@@ -246,6 +938,58 @@ func testBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody
 	assert.Error(t, err)
 }
 
+func testBodyBindingUseNumber(t *testing.T, b Binding, name, path, badPath, body, badBody string) {
+	assert.Equal(t, b.Name(), name)
+
+	obj := FooStructUseNumber{}
+	req := requestWithBody("POST", path, body)
+	EnableDecoderUseNumber = true
+	err := b.Bind(req, &obj)
+	assert.NoError(t, err)
+	// we hope it is int64(123)
+	v, e := obj.Foo.(json.Number).Int64()
+	assert.NoError(t, e)
+	assert.Equal(t, v, int64(123))
+
+	obj = FooStructUseNumber{}
+	req = requestWithBody("POST", badPath, badBody)
+	err = JSON.Bind(req, &obj)
+	assert.Error(t, err)
+}
+
+func testBodyBindingUseNumber2(t *testing.T, b Binding, name, path, badPath, body, badBody string) {
+	assert.Equal(t, b.Name(), name)
+
+	obj := FooStructUseNumber{}
+	req := requestWithBody("POST", path, body)
+	EnableDecoderUseNumber = false
+	err := b.Bind(req, &obj)
+	assert.NoError(t, err)
+	// it will return float64(123) if not use EnableDecoderUseNumber
+	// maybe it is not hoped
+	assert.Equal(t, obj.Foo, float64(123))
+
+	obj = FooStructUseNumber{}
+	req = requestWithBody("POST", badPath, badBody)
+	err = JSON.Bind(req, &obj)
+	assert.Error(t, err)
+}
+
+func testBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body, badBody string) {
+	assert.Equal(t, b.Name(), name)
+
+	obj := FooStruct{}
+	req := requestWithBody("POST", path, body)
+	err := b.Bind(req, &obj)
+	assert.Error(t, err)
+	assert.Equal(t, obj.Foo, "")
+
+	obj = FooStruct{}
+	req = requestWithBody("POST", badPath, badBody)
+	err = JSON.Bind(req, &obj)
+	assert.Error(t, err)
+}
+
 func testProtoBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody string) {
 	assert.Equal(t, b.Name(), name)
 
@@ -263,6 +1007,30 @@ func testProtoBodyBinding(t *testing.T, b Binding, name, path, badPath, body, ba
 	assert.Error(t, err)
 }
 
+type hook struct{}
+
+func (h hook) Read([]byte) (int, error) {
+	return 0, errors.New("error")
+}
+
+func testProtoBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body, badBody string) {
+	assert.Equal(t, b.Name(), name)
+
+	obj := example.Test{}
+	req := requestWithBody("POST", path, body)
+
+	req.Body = ioutil.NopCloser(&hook{})
+	req.Header.Add("Content-Type", MIMEPROTOBUF)
+	err := b.Bind(req, &obj)
+	assert.Error(t, err)
+
+	obj = example.Test{}
+	req = requestWithBody("POST", badPath, badBody)
+	req.Header.Add("Content-Type", MIMEPROTOBUF)
+	err = ProtoBuf.Bind(req, &obj)
+	assert.Error(t, err)
+}
+
 func testMsgPackBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody string) {
 	assert.Equal(t, b.Name(), name)
 

+ 13 - 0
coverage.sh

@@ -0,0 +1,13 @@
+#!/usr/bin/env bash
+
+set -e
+
+echo "mode: count" > coverage.out
+
+for d in $(go list ./... | grep -E 'gin$|binding$|render$'); do
+    go test -v -covermode=count -coverprofile=profile.out $d
+    if [ -f profile.out ]; then
+        cat profile.out | grep -v "mode:" >> coverage.out
+        rm profile.out
+    fi
+done

+ 174 - 1
render/render_test.go

@@ -7,7 +7,9 @@ package render
 import (
 	"bytes"
 	"encoding/xml"
+	"errors"
 	"html/template"
+	"net/http"
 	"net/http/httptest"
 	"testing"
 
@@ -24,6 +26,9 @@ func TestRenderMsgPack(t *testing.T) {
 		"foo": "bar",
 	}
 
+	(MsgPack{data}).WriteContentType(w)
+	assert.Equal(t, w.Header().Get("Content-Type"), "application/msgpack; charset=utf-8")
+
 	err := (MsgPack{data}).Render(w)
 
 	assert.NoError(t, err)
@@ -45,6 +50,9 @@ func TestRenderJSON(t *testing.T) {
 		"foo": "bar",
 	}
 
+	(JSON{data}).WriteContentType(w)
+	assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type"))
+
 	err := (JSON{data}).Render(w)
 
 	assert.NoError(t, err)
@@ -52,6 +60,14 @@ func TestRenderJSON(t *testing.T) {
 	assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type"))
 }
 
+func TestRenderJSONPanics(t *testing.T) {
+	w := httptest.NewRecorder()
+	data := make(chan int)
+
+	// json: unsupported type: chan int
+	assert.Panics(t, func() { (JSON{data}).Render(w) })
+}
+
 func TestRenderIndentedJSON(t *testing.T) {
 	w := httptest.NewRecorder()
 	data := map[string]interface{}{
@@ -66,12 +82,24 @@ func TestRenderIndentedJSON(t *testing.T) {
 	assert.Equal(t, w.Header().Get("Content-Type"), "application/json; charset=utf-8")
 }
 
+func TestRenderIndentedJSONPanics(t *testing.T) {
+	w := httptest.NewRecorder()
+	data := make(chan int)
+
+	// json: unsupported type: chan int
+	err := (IndentedJSON{data}).Render(w)
+	assert.Error(t, err)
+}
+
 func TestRenderSecureJSON(t *testing.T) {
 	w1 := httptest.NewRecorder()
 	data := map[string]interface{}{
 		"foo": "bar",
 	}
 
+	(SecureJSON{"while(1);", data}).WriteContentType(w1)
+	assert.Equal(t, "application/json; charset=utf-8", w1.Header().Get("Content-Type"))
+
 	err1 := (SecureJSON{"while(1);", data}).Render(w1)
 
 	assert.NoError(t, err1)
@@ -91,6 +119,15 @@ func TestRenderSecureJSON(t *testing.T) {
 	assert.Equal(t, "application/json; charset=utf-8", w2.Header().Get("Content-Type"))
 }
 
+func TestRenderSecureJSONFail(t *testing.T) {
+	w := httptest.NewRecorder()
+	data := make(chan int)
+
+	// json: unsupported type: chan int
+	err := (SecureJSON{"while(1);", data}).Render(w)
+	assert.Error(t, err)
+}
+
 type xmlmap map[string]interface{}
 
 // Allows type H to be used with xml.Marshal
@@ -115,12 +152,45 @@ func (h xmlmap) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
 	return e.EncodeToken(xml.EndElement{Name: start.Name})
 }
 
+func TestRenderYAML(t *testing.T) {
+	w := httptest.NewRecorder()
+	data := `
+a : Easy!
+b:
+	c: 2
+	d: [3, 4]
+	`
+	(YAML{data}).WriteContentType(w)
+	assert.Equal(t, w.Header().Get("Content-Type"), "application/x-yaml; charset=utf-8")
+
+	err := (YAML{data}).Render(w)
+	assert.NoError(t, err)
+	assert.Equal(t, w.Body.String(), "\"\\na : Easy!\\nb:\\n\\tc: 2\\n\\td: [3, 4]\\n\\t\"\n")
+	assert.Equal(t, w.Header().Get("Content-Type"), "application/x-yaml; charset=utf-8")
+}
+
+type fail struct{}
+
+// Hook MarshalYAML
+func (ft *fail) MarshalYAML() (interface{}, error) {
+	return nil, errors.New("fail")
+}
+
+func TestRenderYAMLFail(t *testing.T) {
+	w := httptest.NewRecorder()
+	err := (YAML{&fail{}}).Render(w)
+	assert.Error(t, err)
+}
+
 func TestRenderXML(t *testing.T) {
 	w := httptest.NewRecorder()
 	data := xmlmap{
 		"foo": "bar",
 	}
 
+	(XML{data}).WriteContentType(w)
+	assert.Equal(t, w.Header().Get("Content-Type"), "application/xml; charset=utf-8")
+
 	err := (XML{data}).Render(w)
 
 	assert.NoError(t, err)
@@ -129,7 +199,30 @@ func TestRenderXML(t *testing.T) {
 }
 
 func TestRenderRedirect(t *testing.T) {
-	// TODO
+	req, err := http.NewRequest("GET", "/test-redirect", nil)
+	assert.NoError(t, err)
+
+	data1 := Redirect{
+		Code:     301,
+		Request:  req,
+		Location: "/new/location",
+	}
+
+	w := httptest.NewRecorder()
+	err = data1.Render(w)
+	assert.NoError(t, err)
+
+	data2 := Redirect{
+		Code:     200,
+		Request:  req,
+		Location: "/new/location",
+	}
+
+	w = httptest.NewRecorder()
+	assert.Panics(t, func() { data2.Render(w) })
+
+	// only improve coverage
+	data2.WriteContentType(w)
 }
 
 func TestRenderData(t *testing.T) {
@@ -149,6 +242,12 @@ func TestRenderData(t *testing.T) {
 func TestRenderString(t *testing.T) {
 	w := httptest.NewRecorder()
 
+	(String{
+		Format: "hello %s %d",
+		Data:   []interface{}{},
+	}).WriteContentType(w)
+	assert.Equal(t, w.Header().Get("Content-Type"), "text/plain; charset=utf-8")
+
 	err := (String{
 		Format: "hola %s %d",
 		Data:   []interface{}{"manu", 2},
@@ -159,6 +258,19 @@ func TestRenderString(t *testing.T) {
 	assert.Equal(t, w.Header().Get("Content-Type"), "text/plain; charset=utf-8")
 }
 
+func TestRenderStringLenZero(t *testing.T) {
+	w := httptest.NewRecorder()
+
+	err := (String{
+		Format: "hola %s %d",
+		Data:   []interface{}{},
+	}).Render(w)
+
+	assert.NoError(t, err)
+	assert.Equal(t, w.Body.String(), "hola %s %d")
+	assert.Equal(t, w.Header().Get("Content-Type"), "text/plain; charset=utf-8")
+}
+
 func TestRenderHTMLTemplate(t *testing.T) {
 	w := httptest.NewRecorder()
 	templ := template.Must(template.New("t").Parse(`Hello {{.name}}`))
@@ -174,3 +286,64 @@ func TestRenderHTMLTemplate(t *testing.T) {
 	assert.Equal(t, w.Body.String(), "Hello alexandernyquist")
 	assert.Equal(t, w.Header().Get("Content-Type"), "text/html; charset=utf-8")
 }
+
+func TestRenderHTMLTemplateEmptyName(t *testing.T) {
+	w := httptest.NewRecorder()
+	templ := template.Must(template.New("").Parse(`Hello {{.name}}`))
+
+	htmlRender := HTMLProduction{Template: templ}
+	instance := htmlRender.Instance("", map[string]interface{}{
+		"name": "alexandernyquist",
+	})
+
+	err := instance.Render(w)
+
+	assert.NoError(t, err)
+	assert.Equal(t, w.Body.String(), "Hello alexandernyquist")
+	assert.Equal(t, w.Header().Get("Content-Type"), "text/html; charset=utf-8")
+}
+
+func TestRenderHTMLDebugFiles(t *testing.T) {
+	w := httptest.NewRecorder()
+	htmlRender := HTMLDebug{Files: []string{"../fixtures/basic/hello.tmpl"},
+		Glob:    "",
+		Delims:  Delims{Left: "{[{", Right: "}]}"},
+		FuncMap: nil,
+	}
+	instance := htmlRender.Instance("hello.tmpl", map[string]interface{}{
+		"name": "thinkerou",
+	})
+
+	err := instance.Render(w)
+
+	assert.NoError(t, err)
+	assert.Equal(t, w.Body.String(), "<h1>Hello thinkerou</h1>")
+	assert.Equal(t, w.Header().Get("Content-Type"), "text/html; charset=utf-8")
+}
+
+func TestRenderHTMLDebugGlob(t *testing.T) {
+	w := httptest.NewRecorder()
+	htmlRender := HTMLDebug{Files: nil,
+		Glob:    "../fixtures/basic/hello*",
+		Delims:  Delims{Left: "{[{", Right: "}]}"},
+		FuncMap: nil,
+	}
+	instance := htmlRender.Instance("hello.tmpl", map[string]interface{}{
+		"name": "thinkerou",
+	})
+
+	err := instance.Render(w)
+
+	assert.NoError(t, err)
+	assert.Equal(t, w.Body.String(), "<h1>Hello thinkerou</h1>")
+	assert.Equal(t, w.Header().Get("Content-Type"), "text/html; charset=utf-8")
+}
+
+func TestRenderHTMLDebugPanics(t *testing.T) {
+	htmlRender := HTMLDebug{Files: nil,
+		Glob:    "",
+		Delims:  Delims{"{{", "}}"},
+		FuncMap: nil,
+	}
+	assert.Panics(t, func() { htmlRender.Instance("", nil) })
+}