23
loading...
This website collects cookies to deliver better user experience
internal/conf/conf.go
:func NewTestConfig() Config {
testConfig := NewConfig()
testConfig.DbName = testConfig.DbName + "_test"
return testConfig
}
_test
suffix appended to database name. So in our case, test database will be named rgb_test
. To create test database connect to postgres database in the same way as explained in chapter 7 and run:DROP DATABASE IF EXISTS rgb_test;
CREATE DATABASE rgb_test WITH TEMPLATE rgb;
rgb_test
using schema from rgb
. This needs to be executed every time you change development database schema.internal/store/store.go
:func ResetTestDatabase() {
// Connect to test database
SetDBConnection(database.NewDBOptions(conf.NewTestConfig()))
// Empty all tables and restart sequence counters
tables := []string{"users", "posts"}
for _, table := range tables {
_, err := db.Exec(fmt.Sprintf("DELETE FROM %s;", table))
if err != nil {
log.Panic().Err(err).Str("table", table).Msg("Error clearing test database")
}
_, err = db.Exec(fmt.Sprintf("ALTER SEQUENCE %s_id_seq RESTART;", table))
}
}
internal/store/main_test.go
and add helper functions:package store
import (
"rgb/internal/conf"
"rgb/internal/store"
"github.com/gin-gonic/gin"
)
func testSetup() *gin.Engine {
gin.SetMode(gin.TestMode)
store.ResetTestDatabase()
cfg := conf.NewConfig("dev")
jwtSetup(cfg)
return setRouter(cfg)
}
func addTestUser() (*User, error) {
user := &User{
Username: "batman",
Password: "secret123",
}
err := AddUser(user)
return user, err
}
internal/store/users_test.go
and create first test:package store
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestAddUser(t *testing.T) {
testSetup()
user, err := addTestUser()
assert.NoError(t, err)
assert.Equal(t, 1, user.ID)
assert.NotEmpty(t, user.Salt)
assert.NotEmpty(t, user.HashedPassword)
}
func TestAddUserWithExistingUsername(t *testing.T) {
testSetup()
user, err := addTestUser()
assert.NoError(t, err)
assert.Equal(t, 1, user.ID)
user, err = addTestUser()
assert.Error(t, err)
assert.Equal(t, "Username already exists.", err.Error())
}
Authenticate()
function, we will create 3 tests: successful authentication, authenticating with invalid username, and authenticating with invalid password:func TestAuthenticateUser(t *testing.T) {
testSetup()
user, err := addTestUser()
assert.NoError(t, err)
authUser, err := Authenticate(user.Username, user.Password)
assert.NoError(t, err)
assert.Equal(t, user.ID, authUser.ID)
assert.Equal(t, user.Username, authUser.Username)
assert.Equal(t, user.Salt, authUser.Salt)
assert.Equal(t, user.HashedPassword, authUser.HashedPassword)
assert.Empty(t, authUser.Password)
}
func TestAuthenticateUserInvalidUsername(t *testing.T) {
testSetup()
user, err := addTestUser()
assert.NoError(t, err)
authUser, err := Authenticate("invalid", user.Password)
assert.Error(t, err)
assert.Nil(t, authUser)
}
func TestAuthenticateUserInvalidPassword(t *testing.T) {
testSetup()
user, err := addTestUser()
assert.NoError(t, err)
authUser, err := Authenticate(user.Username, "invalid")
assert.Error(t, err)
assert.Nil(t, authUser)
}
FetchUser()
function with 2 tests: successfull fetch, and fetching not existing user:func TestFetchUser(t *testing.T) {
testSetup()
user, err := addTestUser()
assert.NoError(t, err)
fetchedUser, err := FetchUser(user.ID)
assert.NoError(t, err)
assert.Equal(t, user.ID, fetchedUser.ID)
assert.Equal(t, user.Username, fetchedUser.Username)
assert.Empty(t, fetchedUser.Password)
assert.Equal(t, user.Salt, fetchedUser.Salt)
assert.Equal(t, user.HashedPassword, fetchedUser.HashedPassword)
}
func TestFetchNotExistingUser(t *testing.T) {
testSetup()
fetchedUser, err := FetchUser(1)
assert.Error(t, err)
assert.Nil(t, fetchedUser)
assert.Equal(t, "Not found.", err.Error())
}
internal/store/user.go
. You can try to add tests for rest of the files by yourself for practice, or check my solution in RGB GitHub.internal/server/main_test.go
:package server
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"rgb/internal/store"
"strings"
"github.com/gin-gonic/gin"
"github.com/rs/zerolog/log"
)
func testSetup() *gin.Engine {
gin.SetMode(gin.TestMode)
store.ResetTestDatabase()
jwtSetup()
return setRouter()
}
func userJSON(user store.User) string {
body, err := json.Marshal(map[string]interface{}{
"Username": user.Username,
"Password": user.Password,
})
if err != nil {
log.Panic().Err(err).Msg("Error marshalling JSON body.")
}
return string(body)
}
func jsonRes(body *bytes.Buffer) map[string]interface{} {
jsonValue := &map[string]interface{}{}
err := json.Unmarshal(body.Bytes(), jsonValue)
if err != nil {
log.Panic().Err(err).Msg("Error unmarshalling JSON body.")
}
return *jsonValue
}
func performRequest(router *gin.Engine, method, path, body string) *httptest.ResponseRecorder {
req, err := http.NewRequest(method, path, strings.NewReader(body))
if err != nil {
log.Panic().Err(err).Msg("Error creating new request")
}
rec := httptest.NewRecorder()
req.Header.Add("Content-Type", "application/json")
router.ServeHTTP(rec, req)
return rec
}
performRequest()
. In that function we are creating new request using http
package and new recorder using httptest
package. We also need to add Content-Type
header with value application/json
to our test request. We are now ready to serve that test request using passed router and record response with recorder. Let's now see how to use these functions in action. Create new file internal/server/user_test.go
:package server
import (
"net/http"
"rgb/internal/store"
"testing"
"github.com/stretchr/testify/assert"
)
func TestSignUp(t *testing.T) {
router := testSetup()
body := userJSON(store.User{
Username: "batman",
Password: "secret123",
})
rec := performRequest(router, "POST", "/api/signup", body)
assert.Equal(t, http.StatusOK, rec.Code)
assert.Equal(t, "Signed up successfully.", jsonRes(rec.Body)["msg"])
assert.NotEmpty(t, jsonRes(rec.Body)["jwt"])
}
-p 1
option. That means you should run tests using command:go test -p 1 ./internal/...