I have this golang file:
package main
import (
"log"
"sync"
"github.com/jmoiron/sqlx"
)
var db *sqlx.DB
var once sync.Once
// GetDBConnection whatever
func GetDBConnection() {
once.Do(func() {
db, err := sqlx.Connect("postgres", "user=tom dbname=jerry password=myPassword sslmode=disable")
if err != nil {
log.Fatalln(err)
}
})
return db // <<< error here
}
I get this error:
Too many arguments to return
I am just trying to create a singleton pattern and return the db connection. I am not sure if what is returned from sqlx.Connect is type sqlx.DB, that might be the problem. Is there a quick way to determine the return type of sqlx.Connect()?
You've declared the function GetDBConnection() to return no arguments.
func GetDBConnection() {
You have to tell Go the type of the argument you intend to return:
func GetDBConnection() *sqlx.DB {
As for determining the type, I just went to look at the source code. You could also look at the documentation on godoc.org, which is auto-generated from publicly available Go packages.
Related
I try to implement some unit testing in my go code and find the topic of mocking method quite difficult.
I have the following example where I hope you can help me :)
On the first layer I have the following code:
package api
import (
"fmt"
"core"
)
type createUserDTO struct {
Id string
}
func ApiMethod() {
fmt.Println("some incoming api call wit user")
incomingUserData := &createUserDTO{Id: "testId"}
mapedUser := incomingUserData.mapUser()
mapedUser.Create()
}
func (createUserDTO *createUserDTO) mapUser() core.User {
return &core.UserCore{Id: createUserDTO.Id}
}
The second layer has the following code:
package core
import (
"fmt"
)
type CoreUser struct{ Id string }
type User interface {
Create()
}
func (user CoreUser) Create() {
fmt.Println("Do Stuff")
}
My question now is, how do I test every public method in the api package without testing the core package. Especially the method Create().
Based on the comments, I put together a trivial GitHub repository to show how I usually deal with structuring projects in Go. The repository doesn't take into consideration the test part for now but it should be pretty easy to insert them with this project structure.
Let's start with the general folders' layout:
controllers
services
db
dto
models
Now, let's see the relevant files.
models/user.go
package models
import "gorm.io/gorm"
type User struct {
*gorm.Model
Id string `gorm:"primaryKey"`
}
func NewUser(id string) *User {
return &User{Id: id}
}
Simple struct definition here.
dto/user.go
package dto
import "time"
type UserDTO struct {
Id string `json:"id"`
AddedOn time.Time `json:"added_on"`
}
func NewUserDTO(id string) *UserDTO {
return &UserDTO{Id: id}
}
We enrich the model struct with a dummy AddedOn field which needs only for the sake of the demo.
db/user.go
package db
import (
"gorm.io/gorm"
"userapp/models"
)
type UserDb struct {
Conn *gorm.DB
}
type UserDbInterface interface {
SaveUser(user *models.User) error
}
func (u *UserDb) SaveUser(user *models.User) error {
if dbTrn := u.Conn.Create(user); dbTrn.Error != nil {
return dbTrn.Error
}
return nil
}
Here, we define an interface for using the User repository. In our tests, we can provide a mock instead of an instance of the UserDb struct.
services/user.go
package services
import (
"time"
"userapp/db"
"userapp/dto"
"userapp/models"
)
type UserService struct {
DB db.UserDbInterface
}
type UserServiceInterface interface {
AddUser(inputReq *dto.UserDTO) (*dto.UserDTO, error)
}
func NewUserService(db db.UserDbInterface) *UserService {
return &UserService{
DB: db,
}
}
func (u *UserService) AddUser(inputReq *dto.UserDTO) (*dto.UserDTO, error) {
// here you can write complex logic
user := models.NewUser(inputReq.Id)
// invoke db repo
if err := u.DB.SaveUser(user); err != nil {
return nil, err
}
inputReq.AddedOn = time.Now()
return inputReq, nil
}
This is the layer that bridges connections between the presentation layer and the underlying repositories.
controllers/user.go
package controllers
import (
"encoding/json"
"io"
"net/http"
"userapp/dto"
"userapp/services"
)
type UserController struct {
US services.UserServiceInterface
}
func NewUserController(userService services.UserServiceInterface) *UserController {
return &UserController{
US: userService,
}
}
func (u *UserController) Save(w http.ResponseWriter, r *http.Request) {
reqBody, err := io.ReadAll(r.Body)
if err != nil {
panic(err)
}
r.Body.Close()
var userReq dto.UserDTO
json.Unmarshal(reqBody, &userReq)
userRes, err := u.US.AddUser(&userReq)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
json.NewEncoder(w).Encode(err)
return
}
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(userRes)
}
Here, we defined the controller that (through Dependency Injection) uses the UserService struct.
You can find everything in my repository on GitHub
Let me know if it clarifies a little bit.
I'm fairly new to Go and I'm having some issues with writing tests, specifically mocking the response of a package function.
I'm writing an wrapper lib for github.com/go-redis/redis. At the moment it only really has better errors for failures, but it will be expanded with statsd tracking further down the line, but I digress...
I have the following go package that I have created
package myredis
import (
"time"
"github.com/go-redis/redis"
errors "github.com/pkg/errors"
)
var newRedisClient = redis.NewClient
// Options - My Redis Connection Options
type Options struct {
*redis.Options
DefaultLifetime time.Duration
}
// MyRedis - My Redis Type
type MyRedis struct {
options Options
client *redis.Client
}
// Connect - Connects to the Redis Server. Returns an error on failure
func (r *MyRedis) Connect() error {
r.client = newRedisClient(&redis.Options{
Addr: r.options.Addr,
Password: r.options.Password,
DB: r.options.DB,
})
_, err := r.client.Ping().Result()
if err != nil {
return errors.Wrap(err, "myredis")
}
return nil
}
My problem is that I want redis.NewClient to return a mock. This is the test code that I wrote, but it's not working:
package myredis
import (
"testing"
"github.com/go-redis/redis"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
type redisStatusCmdMock struct {
mock.Mock
}
func (m *redisStatusCmdMock) Result() (string, error) {
args := m.Called()
return args.Get(0).(string), args.Error(1)
}
type redisClientMock struct {
mock.Mock
}
func (m *redisClientMock) Ping() redis.StatusCmd {
args := m.Called()
return args.Get(0).(redis.StatusCmd)
}
func TestConnect(t *testing.T) {
assert := assert.New(t)
old := newRedisClient
defer func() { newRedisClient = old }()
newRedisClient = func(options *redis.Options) *redis.Client {
assert.Equal("127.0.0.1:1001", options.Addr)
assert.Equal("password", options.Password)
assert.Equal(1, options.DB)
statusCmdMock := new(redisStatusCmdMock)
statusCmdMock.On("Result").Return("success", nil)
clientMock := new(redisClientMock)
clientMock.On("Ping").Return(statusCmdMock)
return clientMock
}
options := Options{}
options.Addr = "127.0.0.1:1001"
options.Password = "password"
options.DB = 1
r := MyRedis{options: options}
result, err := r.Connect()
assert.Equal("success", result)
assert.Equal(nil, err)
}
I get the following error: cannot use clientMock (type *redisClientMock) as type *redis.Client in return argument. I think I read that I need to mock all the functions of redis.Client in order to be able to use it as a mock in this case, but is that really the case? That seems like it's overkill and I should be able to do this in some way. How do I go about getting this test to work, or do I need to restructure my code so that it's easier to write the test?
redis.Client is a struct type and in Go struct types are simply not mockable. However interfaces in Go are mockable, so what you can do is to define your own "newredisclient" func that instead of returning a struct returns an interface. And since interfaces in Go are satisfied implicitly you can define your interface such that it will be implemented by redis.Client out of the box.
type RedisClient interface {
Ping() redis.StatusCmd
// include any other methods that you need to use from redis
}
func NewRedisCliennt(options *redis.Options) RedisClient {
return redis.NewClient(options)
}
var newRedisClient = NewRedisClient
If you also want to mock the return value from Ping(), you need to do a bit more work.
// First define an interface that will replace the concrete redis.StatusCmd.
type RedisStatusCmd interface {
Result() (string, error)
// include any other methods that you need to use from redis.StatusCmd
}
// Have the client interface return the new RedisStatusCmd interface
// instead of the concrete redis.StatusCmd type.
type RedisClient interface {
Ping() RedisStatusCmd
// include any other methods that you need to use from redis.Client
}
Now *redis.Client does not satisfy the RedisClient interface anymore because the return type of Ping() is different. Note that it doesn't matter that the result type of redis.Client.Ping() satisfies the interface type returned by RedisClient.Ping(), what matters is that the method signatures are different and therefore their types are different.
To fix this you can define a thin wrapper that uses *redis.Client directly and also satisfies the new RedisClient interface.
type redisclient struct {
rc *redis.Client
}
func (c *redisclient) Ping() RedisStatusCmd {
return c.rc.Ping()
}
func NewRedisCliennt(options *redis.Options) RedisClient {
// here wrap the *redis.Client into *redisclient
return &redisclient{redis.NewClient(options)}
}
var newRedisClient = NewRedisClient
I'm attempting to query a database given a model, and I'm getting a blank model as a response. I'm using Revel framework and GORM to create the model and query the database.
app.go
package controllers
import (
"github.com/revel/revel"
"route/to/models"
)
type App struct {
*revel.Controller
GormController
}
func (c App) Index() revel.Result {
accounts := models.Account{}
account := c.DB.Find(accounts)
return c.RenderJSON(account)
}
gorm.go
package controllers
import (
_ "github.com/jinzhu/gorm/dialects/postgres"
"github.com/jinzhu/gorm"
r "github.com/revel/revel"
)
type GormController struct {
*r.Controller
DB *gorm.DB
}
// it can be used for jobs
var Gdb *gorm.DB
// init db
func InitDB() {
var err error
// open db
Gdb, err = gorm.Open("postgres", "host=hostname port=5432 user=postgres password=password dbname=some_db_name sslmode=disable")
Gdb.LogMode(true) // Print SQL statements
if err != nil {
r.ERROR.Println("FATAL", err)
panic(err)
}
}
func (c *GormController) SetDB() r.Result {
c.DB = Gdb
return nil
}
Not sure where I'm going wrong, ultimately I want to do a select * from accounts;
Note: I can run a migration and create in the Index() function and the data is handled correctly.
When populating a struct from the database, you need to pass a pointer for GORM to be able to populate it. Otherwise it's pass-by-value and GORM populates a copy and you never see it.
And if you want to get all accounts you should pass a pointer to a slice of accounts. Regarding pointers versus values, a slice is different from a struct in that the underlying array can still be modified even in pass-by-value, but outside the function the slice length (and capacity) won't change even as the array is populated within the function, so it still won't behave as expected without a pointer.
And you should check Error afterward:
accounts := make([]models.Account, 0)
if err := c.DB.Find(&accounts).Error; err != nil {
if gorm.IsRecordNotFoundError(err) {
// handle not found
} else {
// print/log/return error
}
return
}
// do something with accounts
I'm trying to return a instance from the gorm.Open() return it i'm getting following error
controllers/db.go:34: cannot assign *gorm.DB to dc.DB (type gorm.DB) in multiple assignment
This is the db.go controller
package controllers
import (
//"fmt"
_ "github.com/go-sql-driver/mysql"
//v "github.com/spf13/viper"
"github.com/jinzhu/gorm"
)
type DBController struct {
DB gorm.DB
}
func (dc *DBController) InitDB() {
var err error
dc.DB, err = gorm.Open("mysql","root:12345#tcp(localhost:3306)/api")
if err != nil {
log.Fatalf("Error when connect database, the error is '%v'", err)
}
dc.DB.LogMode(true)
}
func (dc *DBController) GetDB() gorm.DB {
return dc.DB
}
What is reason for this error and how can i fix this?
You need and most probably you want to have a pointer in the structure of controller. Passing structure with a pointer to the database object (gorm.DB) will prevent Go from making a copy of this object (gorm.DB).
Do the following:
type DBController struct {
DB *gorm.DB
}
Now it should work fine.
I have a database package that contains the following code.
package database
import (
"log"
"github.com/jinzhu/gorm"
// required by gorm
_ "github.com/mattn/go-sqlite3"
)
type Podcast struct {
ID int `sql:"index"`
Title string
RssURL string `sql:"unique_index"`
Paused bool
Episodes []Episode
}
type Episode struct {
ID int `sql:"index"`
PodcastID int
Title string
EnclosureURL string `sql:"unique_index"`
Downloaded bool
GUID string `sql:"unique_index"`
PubDate string
}
func DBSession() (db gorm.DB) {
sqliteSession, err := gorm.Open("sqlite3", cache.db)
if err != nil {
log.Fatal(err)
}
return sqliteSession
}
Followed by a bunch of methods that all start with the following code.
FindSomethingByID(id int) {
db := DBSession()
db.LogMode(false)
// code
}
FindSomethingElse {
db := DBSession()
db.LogMode(false)
// code
}
Calling DBSession and setting LogMode in each func seems bad. I just don't know how to do it better. Could someone help?
Calling gorm.Open inside every function isn't very efficient: Open opens a new connection pool, and should be called just once (see the database/sql docs, which gorm wraps).
A simple improvement is to establish a global gorm.DB, initialise it in init() it from all of your functions - e.g.
package database
var db gorm.DB
func init() {
var err error
// Note we use an = and not a := as our variables
// are already initialised
db, err = gorm.Open("sqlite3", "cache.db")
if err != nil {
log.Fatal(err)
}
// Turn off logging globally
db.LogMode(false)
}
FindSomethingByID(id int) {
err := db.Query("...")
// code
}
This is a quick win and reduces the repetition.
In a larger application it typically makes sense to pass dependencies (like DB pools, config params, etc.) more explicitly by wrapping them in types and creating custom handlers.
You also might initialise the connection in your package main and pass the *gorm.DB to your database package via a func New(db *gorm.DB) function that sets a private, package-level variable.
The most obvious simplification would be to move the db.LogMode(false) call into the DBSession() function, and give DBSession() a shorter name like DB():
func DB() (db gorm.DB) {
sqliteSession, err := gorm.Open("sqlite3", cache.db)
if err != nil {
log.Fatal(err)
}
sqliteSession.LogMode(false)
return sqliteSession
}
And using it:
FindSomethingByID(id int) {
db := DB()
// code
}
Now there's only 1 line in each of your functions using the db session, one simple function call. You can't really make it any shorter if you always need a new db session.