从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 ...
随机推荐
- Mac下配置idea(Mac 10.12)
idea应该是第二个最好用的开发工具,除了宇宙最强大的VS第一外,过来就是它,其体系中已经发布很多语言的开发工具. idea:http://bbs.feng.com/read-htm-tid-1050 ...
- wampserver实现外网访问
1.打开运行WampServer3.0.4,鼠标移到wampserver上去,单击右键,出来个wamp Settings, 按照如图所示,选择Menu item : Online / Offline. ...
- Paths
Paths 在javax.tools.StandardLocation 枚举类下定义了几个枚举变量,如下: public enum StandardLocation implements Locati ...
- Android Studio: /dev/kvm device permission denied
https://stackoverflow.com/questions/37300811/android-studio-dev-kvm-device-permission-denied To chec ...
- 在超链接href中实现form的提交
<form name="form1" method="post" action=""> <div class=" ...
- java当中的定时器
对于开发游戏项目的同胞来说,Timer 这个东西肯定不会陌生,今天对以前自己经常使用的定时进行了一番小小的总结!没有写具体实现的原理,只是列举出了其中的四种比较常见的使用方法,相对而言,所以只要按照其 ...
- IDEA的学习总结
IntelliJ IDEA是一款非常优秀的JAVA编辑器,初学都可会对其中的一些做法感到很别扭,刚开始用的时候我也感到很不习惯,在参考了网上一些文章后在这里把我的一些经验写出来,希望初学者能快速适应它 ...
- WEB Front-end Development Technology
1.Einführung der HTML(Hypertext Markup Language) 1.1 Grundlegendes Label 1.1.1 Block Label <h1> ...
- 每天一道剑指offer-二叉树的下一个结点
题目 每天一道剑指offer-二叉树的下一个结点 https://www.nowcoder.com/practice/ef068f602dde4d28aab2b210e859150a?tpId=13& ...
- 【总结】java 后台文件上传整理
public Map<String,String> clientUploadAttachment(Long belongId, String fileSource, MultipartFi ...