从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)
}
从golang-gin-realworld-example-app项目学写httpapi (八)的更多相关文章
- 从golang-gin-realworld-example-app项目学写httpapi (七)
https://github.com/gothinkster/golang-gin-realworld-example-app/blob/master/hello.go main调用 package ...
- 从golang-gin-realworld-example-app项目学写httpapi (六)
https://github.com/gothinkster/golang-gin-realworld-example-app/blob/master/users/validators.go 验证器 ...
- 从golang-gin-realworld-example-app项目学写httpapi (五)
https://github.com/gothinkster/golang-gin-realworld-example-app/blob/master/users/middlewares.go 中间件 ...
- 从golang-gin-realworld-example-app项目学写httpapi (四)
https://github.com/gothinkster/golang-gin-realworld-example-app/blob/master/users/routers.go 路由定义 pa ...
- 从golang-gin-realworld-example-app项目学写httpapi (三)
https://github.com/gothinkster/golang-gin-realworld-example-app/blob/master/users/serializers.go 序列化 ...
- 从golang-gin-realworld-example-app项目学写httpapi (二)
https://github.com/gothinkster/golang-gin-realworld-example-app/blob/master/users/models.go 模型定义 use ...
- 从golang-gin-realworld-example-app项目学写httpapi (一)
https://wangzitian0.github.io/2013/06/29/zero-to-one-1/ https://github.com/gothinkster/golang-gin-re ...
- Golang Gin 项目包依赖管理 godep 使用
Golang Gin 项目包依赖管理 godep 使用 标签(空格分隔): Go 在按照github.com/tools/godep文档go get完包以后,调整项目结构为$GOPATH/src/$P ...
- Golang Gin 项目使用 Swagger
Golang Gin 项目使用 Swagger 标签(空格分隔): Go 首先需要github.com/swaggo/gin-swagger和github.com/swaggo/gin-swagger ...
随机推荐
- ToolkitScriptManager vs. ScriptManager 关于“只能向页面中添加 ScriptManager 的一个实例”讨论
在使用ASP.NET设计AJAX功能网页时,需要首先声明ToolkitScriptManager或者ScriptManager控件,这些全局的脚本核心控制,然后才能使用众多的AJAX控件.如果没有创建 ...
- css hack 汇整
针对Chrome和Safari等Webkit核心浏览器的CSS hack代码: @media screen and (-webkit-min-device-pixel-ratio:0) { …… } ...
- python-pymongo使用
#-*- coding: utf-8 -*- #python2.7x from pymongo import MongoClient def get_db(): #建立连接 client = Mong ...
- elixir中的truth和true
在elixir中, true 就是true 或者是:true 是一个原子 atom, 在其他语言中的true,这里叫做truth, 只要你不是false,nil ,就是truth, 当然 false和 ...
- 定时删除elasticsearch索引
从去年搭建了日志系统后,就没有去管它了,最近发现大半年各种日志的index也蛮多的,就想着写个脚本定时清理一下,把一些太久的日志清理掉. 脚本思路:通过获取index的尾部时间与我们设定的过期时间进行 ...
- CNN理解与实现
CNN理解与实现 组成部分 Convolution Layer Pool Layer: Max-pooling layer Average-pooling layer Full Connected(F ...
- 说一说HTTP
什么是URI和URL URI用字符串标示某一互联网资源,而URL表示资源的地点.可见URL是URI的子集. URI要使用涵盖全部必要信息的URI.绝对URL以及相对URL.相对URL是指从浏览器中基本 ...
- UICollectionView二级树展开
公司项目是社区类的,上周就下载了些社区类APP看了下,发现小区无忧首页的顶部蛮好玩,就试着做了一下,现在先把UICollectionView的二级树展开功能分享一下 . 1.效果图 2.创建子Coll ...
- this,小心!
this是面向对象语言中的一个重要概念,在JAVA,C#等大型语言中,this固定指向运行时的当前对象.但是在JS中,由于 javascript的动态性(解释执行,当然也有简单的预编译过程),this ...
- 新装iis 页面503错误 DefaultAppPool停止解决方案
配置:应用程序池-选择要配置的应用池-高级设置-标识-选择LocalSystem