Mocking functions called from within another in golang - go

I am trying to stub os.Stat and ioutil.ReadFile(path) as used the code below or if you like here on go playground [1]
package main
import (
"fmt"
"io/ioutil"
"os"
"strings"
)
func AssignFileValueFrom(path string, val *string) {
var (
tempValue []byte
err error
)
if _, err = os.Stat(path); err == nil {
if err != nil {
fmt.Println("There was a os stat error:", err)
}
tempValue, err = ioutil.ReadFile(path)
if err != nil {
fmt.Println("There was an io read error:", err)
}
*val = strings.TrimSpace(string(tempValue))
}
}
I have used testify and tried following the example here [2]
package main
import (
"testing"
"github.com/stretchr/testify/mock"
)
type osMock struct {
mock.Mock
}
func (o osMock) Stat(path string) (interface{}, error) {
return nil, nil
}
func TestAssignFileValueFrom(t *testing.T) {
var test string
osm := new(osMock)
osm.On(`Stat`, `./.test`).Return([]byte(`1`), nil)
AssignFileValueFrom(`./.test`, &test)
// assert.Equal(t, `1`, test)
osm.AssertExpectations(t)
}
What am I not doing correctly??
[1] https://play.golang.org/p/xcbdMkMwoBN
[2] https://github.com/stretchr/testify#mock-package

Your code with osMock doesn't any how influence execution of AssignFileValueFrom function. There is a direct call of os.Stat and it won't be substituted just because you have declared osMock somewhere.
To do actual testing you should use interfaces and dependency injection to be able to test your code.
First of all os.Stat call must be substituted with call to your struct that implements an interface with same method defined. And you need to create at least 2 implementations of this interface: 1 - is actual working code to use, 2 - mock as your osMock struct to use in test. And you need to inject it or pass it to AssignFileValueFrom and then use to call Stat method on it.

Thanks, guys for your inputs I have rewritten my tests as follows..
package main
import (
"io/ioutil"
"os"
"testing"
"github.com/stretchr/testify/assert"
)
var (
err error
testFile *os.File
test string
)
const (
TestPrefix = `test_file_prefix`
FileContent = `1234`
)
func init() {
testFile, err = ioutil.TempFile(os.TempDir(), TestPrefix)
if err != nil {
panic(err)
}
err = ioutil.WriteFile(testFile.Name(), []byte(FileContent), 0644)
if err != nil {
panic(err)
}
}
func TestAssignFileValueFrom(t *testing.T) {
AssignFileValueFrom(testFile.Name(), &test)
assert.Equal(t, test, FileContent)
}

Related

Extract filename from io.ReadCloser

I need to get the filename of certain file(s) that receives backend from the frontend. Backend (implemented in Go) will receive the file as io.ReadCloser. Is there way I could extract it from the io.ReadCloser?
Backend (implemented in Go) will receive the file as io.ReadCloser. Is there way I could extract it from the io.ReadCloser?
No.
Take a look at which methods an io.ReadCloser provides by running go doc io.ReadCloser and note that there isn't a method which will provide a name. So unless you know nothing more that that it is an io.ReadCloser you simply cannot do it.
package main
import (
"errors"
"fmt"
"io"
"os"
)
func fatalln(err error) {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
// hasName interface is an interface that expects types
// that implements it to have "Name() string" method.
type hasName interface {
Name() string
}
func open(name string) (io.ReadCloser, error) {
f, err := os.Open(name)
if err != nil {
return nil, err
}
// f implements io.ReadCloser interface as *os.File
// has Read and Close methods.
return f, nil
}
func main() {
// rc is of the type io.ReadCloser
rc, err := open("example.txt")
if err != nil {
fatalln(err)
}
defer rc.Close()
// Type assetion to check rc's underlying type has
// a method "Name() string".
f, ok := rc.(hasName)
if !ok {
fatalln(errors.New("type assertion failed"))
}
// Yay, type assertion succeeded. Print the name!
fmt.Println("Name:", f.Name())
}
The io.ReadCloser here is a reader for runtime reader which reads file from network as the frontend sends it to backend. You'll have to work on request itself to get that file name.
This is an assumption but in most such cases for file upload, the request is a multipart request. If you have the same situation, you can read the headers, typically Content-Disposition to identify the file type. Go native http.Request has ability to parse the details. You can try this :
formFile, handler, err := r.FormFile("file") // read file from network with key "file"
defer formFile.Close()
fileName := handler.Filename // Get file name
By defining an interface which embeds io.Reader you can require a Name() method up front:
package main
import (
"fmt"
"io"
"log"
"os"
)
type NamedReadCloser interface {
io.ReadCloser
Name() string
}
func doThings(f NamedReadCloser) error {
defer f.Close()
b, err := io.ReadAll(f)
if err != nil {
return err
}
fmt.Printf("Name: %s, Content: %s\n", f.Name(), b)
return nil
}
func main() {
f, err := os.Open("/etc/hosts")
if err != nil {
log.Fatal("Cannot open file: ", err)
}
err = doThings(f)
if err != nil {
log.Fatal("Error doing things: ", err)
}
}
This will only work if what is passed in has a name method, like an *os.File. If it does not, then what you are trying to do is not possible.
You'll have to cast it to a type with a Name method:
package main
import (
"io"
"os"
)
func open(name string) (io.ReadCloser, error) {
return os.Open(name)
}
func main() {
c, e := open("file.txt")
if e != nil {
panic(e)
}
defer c.Close()
f := c.(*os.File)
println(f.Name())
}

Initializing error variable as error pointer

Trying to handle all error in a single defer function.
But err can't be assigned as a pointer to error? As it gives me Invalid memory address error
package main
import (
"fmt"
"errors"
)
func main() {
var err *error
defer func(err *error) {
if *err != nil {
fmt.Println("hi")
} else {
fmt.Println("oh")
}
}(err)
*err = errors.New("EMPTY_BODY")
}
playground
you need to assign the memory to the err where you are just defining now.
Replace var err *error with var err = new(error) or err := new(error) to instantiate and make this code work.
I never needed to pass pointer
package main
import (
"fmt"
"errors"
)
func main() {
var err error
defer func() {
if err != nil {
fmt.Println("hi")
} else {
fmt.Println("oh")
}
}()
err = errors.New("EMPTY_BODY")
}
I thought i needed as defer was taking the value of err at that point not at the end of function.

GORM DB Connection on other package

I start learning Go, reading about pointers, and want to split my database connection , and handler function for API. Already tried myself, by following this solution , but when i trying to read data, i am having this error
[2018-06-26 21:59:45] sql: database is closed
this is my source code.
db.go
package db
import (
"fmt"
"github.com/jinzhu/gorm"
"github.com/joho/godotenv"
"os"
)
var Db *gorm.DB
func Open() error {
var err error
_ = godotenv.Load(".env")
dbType := os.Getenv("DB_TYPE")
dbConnString := os.Getenv("DB_CONN_STRING")
Db, err = gorm.Open(dbType, dbConnString)
if err != nil {
fmt.Println(err)
}
Db.LogMode(true)
defer Db.Close()
return err
}
func Close() error {
return Db.Close()
}
person.go
package model
import (
"github.com/jinzhu/gorm"
"github.com/gin-gonic/gin"
"fmt"
"namastra/gin/result"
"namastra/gin/db"
)
type Person struct {
gorm.Model
FirstName string `json:”firstname”`
LastName string `json:”lastname”`
}
/*var db *gorm.DB
var err error*/
func GetPeople(c *gin.Context) {
var people []result.Person
if err := db.Db.Select("ID,first_name,last_name").Find(&people).Error; err != nil {
c.AbortWithStatus(404)
fmt.Println(err)
} else {
c.JSON(200, people)
}
}
main.go
package main
import (
"log"
"namastra/gin/handler"
"namastra/gin/model"
"net/http"
"time"
"github.com/adam-hanna/jwt-auth/jwt"
"github.com/gin-gonic/gin"
_ "github.com/jinzhu/gorm/dialects/postgres"
"namastra/gin/db"
)
func main() {
if err := db.Open(); err != nil {
// handle error
panic(err)
}
defer db.Close()
router := gin.Default()
router.Use(gin.Recovery())
private := router.Group("/auth")
....(ommited)
router.GET("/", gin.WrapH(regularHandler))
router.GET("/people/", model.GetPeople)
router.Run("127.0.0.1:3000")
}
Sorry for my bad english, any kind of help is appreciated.
thank you.
edit1: case closed.
solution is by removing
defer Db.Close()
from db.go.
edi2: update some knowledge i learn by working in go project
As start learning GO, usually we put everything on single main.go file, and we think to split the code to multiple files.
That is the time Dependency Injection comes to play.
we can create something like this Env to store the handler.
type Env struct {
db *sql.DB
logger *log.Logger
templates *template.Template
}
and create something like this in models/db.go
package models
import (
"database/sql"
_ "github.com/lib/pq"
)
func NewDB(dataSourceName string) (*sql.DB, error) {
db, err := sql.Open("postgres", dataSourceName)
if err != nil {
return nil, err
}
if err = db.Ping(); err != nil {
return nil, err
}
return db, nil
}
main.go files
package main
import (
"namastra/gin/models"
"database/sql"
"fmt"
"log"
"net/http"
)
type Env struct {
db *sql.DB
}
func main() {
db, err := models.NewDB("postgres://user:pass#localhost/bookstore")
if err != nil {
log.Panic(err)
}
env := &Env{db: db}
http.HandleFunc("/peoples", env.peoplesIndex)
http.ListenAndServe(":3000", nil)
}
func (env *Env) peoplesIndex(w http.ResponseWriter, r *http.Request) {
# ...
}
and in models/people.go
package models
import "database/sql"
type Book struct {
Isbn string
Title string
Author string
Price float32
}
func AllPeoples(db *sql.DB) ([]*People, error) {
rows, err := db.Query("SELECT * FROM peoples")
if err != nil {
return nil, err
}
defer rows.Close()
# ... ommited for simplicity
}
you can read the full code & explanation in Alex Edwards post

DB.Exec args always results in an error for my placeholder

I have an SQL script with a variable I want to set from Golang.
SET #foo_bar_invitation_id = ?;
SELECT #foo_bar_invitation_id;
I.e. I want to set ? to "foobar". My code:
package main
import (
"io/ioutil"
"log"
"database/sql"
_ "github.com/go-sql-driver/mysql"
)
type handler struct{ db *sql.DB }
func (h handler) runsql() (err error) {
sqlscript, err := ioutil.ReadFile("script.sql")
if err != nil {
return
}
_, err = h.db.Exec(string(sqlscript), "foobar")
if err != nil {
log.Println(err)
}
return
}
Always results in Error 1064: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'SELECT #foo_bar_invitation_id' at line 2
I test via code generated by gotests:
import (
"database/sql"
"os"
"testing"
_ "github.com/go-sql-driver/mysql"
)
var db *sql.DB
func TestMain(m *testing.M) {
db, _ = sql.Open("mysql", os.Getenv("DSN"))
defer db.Close()
os.Exit(m.Run())
}
func Test_handler_runsql(t *testing.T) {
type fields struct {
db *sql.DB
}
tests := []struct {
name string
fields fields
wantErr bool
}{{
"Check ID can be set",
fields{db: db},
false,
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
h := handler{
db: tt.fields.db,
}
if err := h.runsql(); (err != nil) != tt.wantErr {
t.Errorf("handler.step2runsql() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
My DSN connection string includes ?multiStatements=true&sql_mode=TRADITIONAL.
I'm hoping I just do not understand how DB.Exec args interpolation / placeholder works, but I am finding it difficult to find examples.
The answer was to set interpolate params to true. https://github.com/go-sql-driver/mysql#interpolateparams

How to call Go function of test1 in test2

go file in as below
package goClientLib
import (
....
)
//The following function will read Command Line Inputs and will return 3 strings
func readInput() (string, string, string){
var (clientRequest, clientId, clientPassword string)
argsLen := len(os.Args)
fmt.Println("Arg Length:",argsLen)
if len(os.Args) != 4 {
fmt.Fprintf(os.Stderr, "Usage: %s URL\n", os.Args[0])
os.Exit(1)
} else {
clientRequest = strings.Join(os.Args[1:2],"")
clientId = strings.Join(os.Args[2:3],"")
clientPassword = strings.Join(os.Args[3:4],"")
}
return clientRequest, clientId, clientPassword
}
Now I am trying to use it in Test2.go file as shown below:
package main
import (
"os"
"fmt"
"net/http"
"io"
"log"
"goClientLib"
)
func main() {
clientRequest, clientId, clientPassword := goClientLib.readInput()
host := goClientLib.generateRequest(clientRequest)
fmt.Println("clientRequest:",clientRequest)
fmt.Println("clientId:",clientId)
fmt.Println("clientPassword:",clientPassword)
fmt.Println("host:",host)
response, err := http.Get(host)
if err != nil {
log.Fatal(err)
} else {
defer response.Body.Close()
_, err := io.Copy(os.Stdout, response.Body)
if err != nil {
log.Fatal(err)
}
}
}
I am using Following File Structure
src/test2.go
src/goClientLib/test1.go
But This code Give me following error while running
# command-line-arguments
src\goClientMain.go:15: cannot refer to unexported name goClientLib.readInput
src\goClientMain.go:16: cannot refer to unexported name goClientLib.generateRequest
src\goClientMain.go:16: undefined: goClientLib.generateRequest
As Volker commented, in order to access functions from another package, 1st letter of function name must be capital. In your case, change readInput() to ReadInput() and generateRequest() to GenerateRequest() and make sure GenerateRequest() function is defined in goClientLib package. Chack this for more information.

Resources