• 从golang-gin-realworld-example-app项目学写httpapi (八)


    https://github.com/gothinkster/golang-gin-realworld-example-app/blob/master/common/unit_test.go

    单元测试

    package users
    
    import (
    	"testing"
    
    	"github.com/stretchr/testify/assert"
    
    	"bytes"
    	"fmt"
    
    	"github.com/jinzhu/gorm"
    	"github.com/wangzitian0/golang-gin-starter-kit/common"
    
    	//"gopkg.in/gin-gonic/gin.v1"
    	"net/http"
    	"net/http/httptest"
    	"os"
    	_ "regexp"
    
    	"github.com/gin-gonic/gin"
    )
    
    var image_url = "https://golang.org/doc/gopher/frontpage.png"
    var test_db *gorm.DB
    
    func newUserModel() UserModel {
    	return UserModel{
    		ID:           2,
    		Username:     "asd123!@#ASD",
    		Email:        "wzt@g.cn",
    		Bio:          "heheda",
    		Image:        &image_url,
    		PasswordHash: "",
    	}
    }
    
    func userModelMocker(n int) []UserModel {
    	var offset int
    	test_db.Model(&UserModel{}).Count(&offset)
    	var ret []UserModel
    	for i := offset + 1; i <= offset+n; i++ {
    		image := fmt.Sprintf("http://image/%v.jpg", i)
    		userModel := UserModel{
    			Username: fmt.Sprintf("user%v", i),
    			Email:    fmt.Sprintf("user%v@linkedin.com", i),
    			Bio:      fmt.Sprintf("bio%v", i),
    			Image:    &image,
    		}
    		userModel.setPassword("password123")
    		test_db.Create(&userModel)
    		ret = append(ret, userModel)
    	}
    	return ret
    }
    
    // 单元测试函数 用于测试用户模型功能 包括设置密码 校验密码 关注方面的功能
    func TestUserModel(t *testing.T) {
    	asserts := assert.New(t)
    
    	//Testing UserModel's password feature
    	userModel := newUserModel()
    	err := userModel.checkPassword("")
    	asserts.Error(err, "empty password should return err")
    
    	userModel = newUserModel()
    	err = userModel.setPassword("")
    	asserts.Error(err, "empty password can not be set null")
    
    	userModel = newUserModel()
    	err = userModel.setPassword("asd123!@#ASD")
    	asserts.NoError(err, "password should be set successful")
    	asserts.Len(userModel.PasswordHash, 60, "password hash length should be 60")
    
    	err = userModel.checkPassword("sd123!@#ASD")
    	asserts.Error(err, "password should be checked and not validated")
    
    	err = userModel.checkPassword("asd123!@#ASD")
    	asserts.NoError(err, "password should be checked and validated")
    
    	//Testing the following relationship between users
    	users := userModelMocker(3)
    	a := users[0]
    	b := users[1]
    	c := users[2]
    	asserts.Equal(0, len(a.GetFollowings()), "GetFollowings should be right before following")
    	asserts.Equal(false, a.isFollowing(b), "isFollowing relationship should be right at init")
    	a.following(b)
    	asserts.Equal(1, len(a.GetFollowings()), "GetFollowings should be right after a following b")
    	asserts.Equal(true, a.isFollowing(b), "isFollowing should be right after a following b")
    	a.following(c)
    	asserts.Equal(2, len(a.GetFollowings()), "GetFollowings be right after a following c")
    	asserts.EqualValues(b, a.GetFollowings()[0], "GetFollowings should be right")
    	asserts.EqualValues(c, a.GetFollowings()[1], "GetFollowings should be right")
    	a.unFollowing(b)
    	asserts.Equal(1, len(a.GetFollowings()), "GetFollowings should be right after a unFollowing b")
    	asserts.EqualValues(c, a.GetFollowings()[0], "GetFollowings should be right after a unFollowing b")
    	asserts.Equal(false, a.isFollowing(b), "isFollowing should be right after a unFollowing b")
    
    }
    
    //Reset test DB and create new one with mock data
    func resetDBWithMock() {
    	common.TestDBFree(test_db)
    	test_db = common.TestDBInit()
    	AutoMigrate()
    	userModelMocker(3)
    }
    
    func HeaderTokenMock(req *http.Request, u uint) {
    	req.Header.Set("Authorization", fmt.Sprintf("Token %v", common.GenToken(u)))
    }
    
    //You could write the init logic like reset database code here
    var unauthRequestTests = []struct {
    	init           func(*http.Request)
    	url            string
    	method         string
    	bodyData       string
    	expectedCode   int
    	responseRegexg string
    	msg            string
    }{
    	//Testing will run one by one, so you can combine it to a user story till another init().
    	//And you can modified the header or body in the func(req *http.Request) {}
    
    	//---------------------   Testing for user register   ---------------------
    	{
    		func(req *http.Request) {
    			resetDBWithMock()
    		},
    		"/users/",
    		"POST",
    		`{"user":{"username": "wangzitian0","email": "wzt@gg.cn","password": "jakejxke"}}`,
    		http.StatusCreated,
    		`{"user":{"username":"wangzitian0","email":"wzt@gg.cn","bio":"","image":null,"token":"([a-zA-Z0-9-_.]{115})"}}`,
    		"valid data and should return StatusCreated",
    	},
    	{
    		func(req *http.Request) {},
    		"/users/",
    		"POST",
    		`{"user":{"username": "wangzitian0","email": "wzt@gg.cn","password": "jakejxke"}}`,
    		http.StatusUnprocessableEntity,
    		`{"errors":{"database":"UNIQUE constraint failed: user_models.email"}}`,
    		"duplicated data and should return StatusUnprocessableEntity",
    	},
    	{
    		func(req *http.Request) {},
    		"/users/",
    		"POST",
    		`{"user":{"username": "u","email": "wzt@gg.cn","password": "jakejxke"}}`,
    		http.StatusUnprocessableEntity,
    		`{"errors":{"Username":"{min: 4}"}}`,
    		"too short username should return error",
    	},
    	{
    		func(req *http.Request) {},
    		"/users/",
    		"POST",
    		`{"user":{"username": "wangzitian0","email": "wzt@gg.cn","password": "j"}}`,
    		http.StatusUnprocessableEntity,
    		`{"errors":{"Password":"{min: 8}"}}`,
    		"too short password should return error",
    	},
    	{
    		func(req *http.Request) {},
    		"/users/",
    		"POST",
    		`{"user":{"username": "wangzitian0","email": "wztgg.cn","password": "jakejxke"}}`,
    		http.StatusUnprocessableEntity,
    		`{"errors":{"Email":"{key: email}"}}`,
    		"email invalid should return error",
    	},
    
    	//---------------------   Testing for user login   ---------------------
    	{
    		func(req *http.Request) {
    			resetDBWithMock()
    		},
    		"/users/login",
    		"POST",
    		`{"user":{"email": "user1@linkedin.com","password": "password123"}}`,
    		http.StatusOK,
    		`{"user":{"username":"user1","email":"user1@linkedin.com","bio":"bio1","image":"http://image/1.jpg","token":"([a-zA-Z0-9-_.]{115})"}}`,
    		"right info login should return user",
    	},
    	{
    		func(req *http.Request) {},
    		"/users/login",
    		"POST",
    		`{"user":{"email": "user112312312@linkedin.com","password": "password123"}}`,
    		http.StatusForbidden,
    		`{"errors":{"login":"Not Registered email or invalid password"}}`,
    		"email not exist should return error info",
    	},
    	{
    		func(req *http.Request) {},
    		"/users/login",
    		"POST",
    		`{"user":{"email": "user1@linkedin.com","password": "password126"}}`,
    		http.StatusForbidden,
    		`{"errors":{"login":"Not Registered email or invalid password"}}`,
    		"password error should return error info",
    	},
    	{
    		func(req *http.Request) {},
    		"/users/login",
    		"POST",
    		`{"user":{"email": "user1@linkedin.com","password": "passw"}}`,
    		http.StatusUnprocessableEntity,
    		`{"errors":{"Password":"{min: 8}"}}`,
    		"password too short should return error info",
    	},
    	{
    		func(req *http.Request) {},
    		"/users/login",
    		"POST",
    		`{"user":{"email": "user1@linkedin.com","password": "passw"}}`,
    		http.StatusUnprocessableEntity,
    		`{"errors":{"Password":"{min: 8}"}}`,
    		"password too short should return error info",
    	},
    
    	//---------------------   Testing for self info get & auth module  ---------------------
    	{
    		func(req *http.Request) {
    			resetDBWithMock()
    		},
    		"/user/",
    		"GET",
    		``,
    		http.StatusUnauthorized,
    		``,
    		"request should return 401 without token",
    	},
    	{
    		func(req *http.Request) {
    			req.Header.Set("Authorization", fmt.Sprintf("Tokee %v", common.GenToken(1)))
    		},
    		"/user/",
    		"GET",
    		``,
    		http.StatusUnauthorized,
    		``,
    		"wrong token should return 401",
    	},
    	{
    		func(req *http.Request) {
    			HeaderTokenMock(req, 1)
    		},
    		"/user/",
    		"GET",
    		``,
    		http.StatusOK,
    		`{"user":{"username":"user1","email":"user1@linkedin.com","bio":"bio1","image":"http://image/1.jpg","token":"([a-zA-Z0-9-_.]{115})"}}`,
    		"request should return current user with token",
    	},
    
    	//---------------------   Testing for users' profile get   ---------------------
    	{
    		func(req *http.Request) {
    			resetDBWithMock()
    			HeaderTokenMock(req, 1)
    		},
    		"/profiles/user1",
    		"GET",
    		``,
    		http.StatusOK,
    		`{"profile":{"username":"user1","bio":"bio1","image":"http://image/1.jpg","following":false}}`,
    		"request should return self profile",
    	},
    	{
    		func(req *http.Request) {
    			HeaderTokenMock(req, 2)
    		},
    		"/profiles/user1",
    		"GET",
    		``,
    		http.StatusOK,
    		`{"profile":{"username":"user1","bio":"bio1","image":"http://image/1.jpg","following":false}}`,
    		"request should return correct other's profile",
    	},
    
    	//---------------------   Testing for users' profile update   ---------------------
    	{
    		func(req *http.Request) {
    			resetDBWithMock()
    			HeaderTokenMock(req, 1)
    		},
    		"/profiles/user123",
    		"GET",
    		``,
    		http.StatusNotFound,
    		``,
    		"user should not exist profile before changed",
    	},
    	{
    		func(req *http.Request) {
    			HeaderTokenMock(req, 1)
    		},
    		"/user/",
    		"PUT",
    		`{"user":{"username":"user123","password": "password126","email":"user123@linkedin.com","bio":"bio123","image":"http://hehe/123.jpg"}}`,
    		http.StatusOK,
    		`{"user":{"username":"user123","email":"user123@linkedin.com","bio":"bio123","image":"http://hehe/123.jpg","token":"([a-zA-Z0-9-_.]{115})"}}`,
    		"current user profile should be changed",
    	},
    	{
    		func(req *http.Request) {
    			HeaderTokenMock(req, 1)
    		},
    		"/profiles/user123",
    		"GET",
    		``,
    		http.StatusOK,
    		`{"profile":{"username":"user123","bio":"bio123","image":"http://hehe/123.jpg","following":false}}`,
    		"request should return self profile after changed",
    	},
    	{
    		func(req *http.Request) {},
    		"/users/login",
    		"POST",
    		`{"user":{"email": "user123@linkedin.com","password": "password126"}}`,
    		http.StatusOK,
    		`{"user":{"username":"user123","email":"user123@linkedin.com","bio":"bio123","image":"http://hehe/123.jpg","token":"([a-zA-Z0-9-_.]{115})"}}`,
    		"user should login using new password after changed",
    	},
    	{
    		func(req *http.Request) {
    			HeaderTokenMock(req, 2)
    		},
    		"/user/",
    		"PUT",
    		`{"user":{"password": "pas"}}`,
    		http.StatusUnprocessableEntity,
    		`{"errors":{"Password":"{min: 8}"}}`,
    		"current user profile should not be changed with error user info",
    	},
    
    	//---------------------   Testing for db errors   ---------------------
    	{
    		func(req *http.Request) {
    			resetDBWithMock()
    			HeaderTokenMock(req, 4)
    		},
    		"/user/",
    		"PUT",
    		`{"password": "password321"}}`,
    		http.StatusUnprocessableEntity,
    		`{"errors":{"Email":"{key: email}","Username":"{key: alphanum}"}}`,
    		"test database pk error for user update",
    	},
    	{
    		func(req *http.Request) {
    			HeaderTokenMock(req, 0)
    		},
    		"/user/",
    		"PUT",
    		`{"user":{"username": "wangzitian0","email": "wzt@gg.cn","password": "jakejxke"}}`,
    		http.StatusUnprocessableEntity,
    		`{"errors":{"database":"UNIQUE constraint failed: user_models.email"}}`,
    		"cheat validator and test database connecting error for user update",
    	},
    	{
    		func(req *http.Request) {
    			common.TestDBFree(test_db)
    			test_db = common.TestDBInit()
    
    			test_db.AutoMigrate(&UserModel{})
    			userModelMocker(3)
    			HeaderTokenMock(req, 2)
    		},
    		"/profiles/user1/follow",
    		"POST",
    		``,
    		http.StatusUnprocessableEntity,
    		`{"errors":{"database":"no such table: follow_models"}}`,
    		"test database error for following",
    	},
    	{
    		func(req *http.Request) {
    			HeaderTokenMock(req, 2)
    		},
    		"/profiles/user1/follow",
    		"DELETE",
    		``,
    		http.StatusUnprocessableEntity,
    		`{"errors":{"database":"no such table: follow_models"}}`,
    		"test database error for canceling following",
    	},
    	{
    		func(req *http.Request) {
    			resetDBWithMock()
    			HeaderTokenMock(req, 2)
    		},
    		"/profiles/user666/follow",
    		"POST",
    		``,
    		http.StatusNotFound,
    		`{"errors":{"profile":"Invalid username"}}`,
    		"following wrong user name should return errors",
    	},
    	{
    		func(req *http.Request) {
    			HeaderTokenMock(req, 2)
    		},
    		"/profiles/user666/follow",
    		"DELETE",
    		``,
    		http.StatusNotFound,
    		`{"errors":{"profile":"Invalid username"}}`,
    		"cancel following wrong user name should return errors",
    	},
    
    	//---------------------   Testing for user following   ---------------------
    	{
    		func(req *http.Request) {
    			resetDBWithMock()
    			HeaderTokenMock(req, 2)
    		},
    		"/profiles/user1/follow",
    		"POST",
    		``,
    		http.StatusOK,
    		`{"profile":{"username":"user1","bio":"bio1","image":"http://image/1.jpg","following":true}}`,
    		"user follow another should work",
    	},
    	{
    		func(req *http.Request) {
    			HeaderTokenMock(req, 2)
    		},
    		"/profiles/user1",
    		"GET",
    		``,
    		http.StatusOK,
    		`{"profile":{"username":"user1","bio":"bio1","image":"http://image/1.jpg","following":true}}`,
    		"user follow another should make sure database changed",
    	},
    	{
    		func(req *http.Request) {
    			HeaderTokenMock(req, 2)
    		},
    		"/profiles/user1/follow",
    		"DELETE",
    		``,
    		http.StatusOK,
    		`{"profile":{"username":"user1","bio":"bio1","image":"http://image/1.jpg","following":false}}`,
    		"user cancel follow another should work",
    	},
    	{
    		func(req *http.Request) {
    			HeaderTokenMock(req, 2)
    		},
    		"/profiles/user1",
    		"GET",
    		``,
    		http.StatusOK,
    		`{"profile":{"username":"user1","bio":"bio1","image":"http://image/1.jpg","following":false}}`,
    		"user cancel follow another should make sure database changed",
    	},
    }
    
    // 单元测试函数 用于http接口请求测试
    func TestWithoutAuth(t *testing.T) {
    	asserts := assert.New(t)
    	//You could write the reset database code here if you want to create a database for this block
    	//resetDB()
    
    	r := gin.New()
    	UsersRegister(r.Group("/users"))
    	r.Use(AuthMiddleware(true))
    	UserRegister(r.Group("/user"))
    	ProfileRegister(r.Group("/profiles"))
    	for _, testData := range unauthRequestTests {
    		bodyData := testData.bodyData
    		req, err := http.NewRequest(testData.method, testData.url, bytes.NewBufferString(bodyData))
    		req.Header.Set("Content-Type", "application/json")
    		asserts.NoError(err)
    
    		testData.init(req)
    
    		w := httptest.NewRecorder()
    		r.ServeHTTP(w, req)
    
    		asserts.Equal(testData.expectedCode, w.Code, "Response Status - "+testData.msg)
    		asserts.Regexp(testData.responseRegexg, w.Body.String(), "Response Content - "+testData.msg)
    	}
    }
    
    //This is a hack way to add test database for each case, as whole test will just share one database.
    //You can read TestWithoutAuth's comment to know how to not share database each case.
    func TestMain(m *testing.M) {
    	test_db = common.TestDBInit()
    	AutoMigrate()
    	exitVal := m.Run()
    	common.TestDBFree(test_db)
    	os.Exit(exitVal)
    }
    
  • 相关阅读:
    Unity 游戏框架搭建 2019 (二十九) 方法所在类命名问题诞生的原因
    Unity 游戏框架搭建 2019 (二十七、二十八)弃用的代码警告解决&弃用的代码删除
    Unity 游戏框架搭建 2019 (二十六) 第一轮整理完结
    Unity 游戏框架搭建 2019 (二十五) 类的第一个作用 与 Obselete 属性
    排序算法之冒泡排序
    java中List Array相互转换
    java迭代器浅析
    谈谈java中遍历Map的几种方法
    浅谈java反射机制
    springMvc注解之@ResponseBody和@RequestBody
  • 原文地址:https://www.cnblogs.com/liujitao79/p/10019053.html
Copyright © 2020-2023  润新知