Following the example at https://golang.org/pkg/os/exec/#Cmd.StdoutPipe, suppose I have a function getPerson() defined like so:
package stdoutexample
import (
"encoding/json"
"os/exec"
)
// Person represents a person
type Person struct {
Name string
Age int
}
func getPerson() (Person, error) {
person := Person{}
cmd := exec.Command("echo", "-n", `{"Name": "Bob", "Age": 32}`)
stdout, err := cmd.StdoutPipe()
if err != nil {
return person, err
}
if err := cmd.Start(); err != nil {
return person, err
}
if err := json.NewDecoder(stdout).Decode(&person); err != nil {
return person, err
}
if err := cmd.Wait(); err != nil {
return person, err
}
return person, nil
}
In my 'real' application, the command run can have different outputs, I'd like to write test cases for each of these scenarios. However, I'm not sure how to go about this.
So far all I have is a test case for one case:
package stdoutexample
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestGetPerson(t *testing.T) {
person, err := getPerson()
require.NoError(t, err)
assert.Equal(t, person.Name, "Bob")
assert.Equal(t, person.Age, 32)
}
Perhaps the way to go about this is to split this function into two parts, one which writes the output of the command to a string, and another which decodes the output of a string?
adding to https://stackoverflow.com/a/58107208/9353289,
Instead of writing separate Test functions for every test, I suggest you use a Table Driven Test approach instead.
Here is an example,
func Test_getPerson(t *testing.T) {
tests := []struct {
name string
commandOutput []byte
want Person
}{
{
name: "Get Bob",
commandOutput: []byte(`{"Name": "Bob", "Age": 32}`),
want: Person{
Name: "Bob",
Age: 32,
},
},
{
name: "Get Alice",
commandOutput: []byte(`{"Name": "Alice", "Age": 25}`),
want: Person{
Name: "Alice",
Age: 25,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := getPerson(tt.commandOutput)
require.NoError(t, err)
assert.Equal(t, tt.want.Name, got.Name)
assert.Equal(t, tt.want.Age, got.Age)
})
}
}
Simply adding test cases to the slice, will run all test cases.
I added unit tests by splitting the function into two parts: one which reads the output to a slice of bytes, and one which parses that output to a Person:
package stdoutexample
import (
"bytes"
"encoding/json"
"os/exec"
)
// Person represents a person
type Person struct {
Name string
Age int
}
func getCommandOutput() ([]byte, error) {
cmd := exec.Command("echo", "-n", `{"Name": "Bob", "Age": 32}`)
return cmd.Output()
}
func getPerson(commandOutput []byte) (Person, error) {
person := Person{}
if err := json.NewDecoder(bytes.NewReader(commandOutput)).Decode(&person); err != nil {
return person, err
}
return person, nil
}
The following test cases pass:
package stdoutexample
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestGetPerson(t *testing.T) {
commandOutput, err := getCommandOutput()
require.NoError(t, err)
person, err := getPerson(commandOutput)
require.NoError(t, err)
assert.Equal(t, person.Name, "Bob")
assert.Equal(t, person.Age, 32)
}
func TestGetPersonBob(t *testing.T) {
commandOutput := []byte(`{"Name": "Bob", "Age": 32}`)
person, err := getPerson(commandOutput)
require.NoError(t, err)
assert.Equal(t, person.Name, "Bob")
assert.Equal(t, person.Age, 32)
}
func TestGetPersonAlice(t *testing.T) {
commandOutput := []byte(`{"Name": "Alice", "Age": 25}`)
person, err := getPerson(commandOutput)
require.NoError(t, err)
assert.Equal(t, person.Name, "Alice")
assert.Equal(t, person.Age, 25)
}
where the Bob and Alice test cases simulate different output which can be generated by the command.
your implementation design actively rejects fine grain testing because it does not allow any injection.
However, given the example, besides using a TestTable, there is not much to improve.
Now, on a real workload, you might encounter unacceptable slowdown dues to call to the external binary. This might justify another approach involving a design refactoring to mock and the setup of multiple tests stubs.
To mock your implementation you make use of interface capabilities.
To stub your execution you create a mock that outputs stuff you want to check for.
package main
import (
"encoding/json"
"fmt"
"os/exec"
)
type Person struct{}
type PersonProvider struct {
Cmd outer
}
func (p PersonProvider) Get() (Person, error) {
person := Person{}
b, err := p.Cmd.Out()
if err != nil {
return person, err
}
err = json.Unmarshal(b, &person)
return person, err
}
type outer interface{ Out() ([]byte, error) }
type echo struct {
input string
}
func (e echo) Out() ([]byte, error) {
cmd := exec.Command("echo", "-n", e.input)
return cmd.Output()
}
type mockEcho struct {
output []byte
err error
}
func (m mockEcho) Out() ([]byte, error) {
return m.output, m.err
}
func main() {
fmt.Println(PersonProvider{Cmd: echo{input: `{"Name": "Bob", "Age": 32}`}}.Get())
fmt.Println(PersonProvider{Cmd: mockEcho{output: nil, err: fmt.Errorf("invalid json")}}.Get())
}
Related
I've been trying to write unit tests for my http handler. The code segment is as below:
func (s *Server) handleCreateTicketOption(w http.ResponseWriter, r *http.Request) {
var t ticket.Ticket
body, err := ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, er.ErrInternal.Error(), http.StatusInternalServerError)
return
}
err = json.Unmarshal(body, &t)
if err != nil {
http.Error(w, er.ErrInvalidData.Error(), http.StatusBadRequest)
return
}
ticket, err := s.TicketService.CreateTicketOption(r.Context(), t)
if err != nil {
http.Error(w, er.ErrInternal.Error(), http.StatusInternalServerError)
return
}
res, err := json.Marshal(ticket)
if err != nil {
http.Error(w, er.ErrInternal.Error(), http.StatusInternalServerError)
return
}
log.Printf("%v tickets allocated with name %v\n", t.Allocation, t.Name)
s.sendResponse(w, res, http.StatusOK)
}
Actual logic that interacts with DB. This code segment is invoked by the handler as you can see in the code above. ticket, err := s.TicketService.CreateTicketOption(r.Context(), t)
func (t *TicketService) CreateTicketOption(ctx context.Context, ticket ticket.Ticket) (*ticket.Ticket, error) {
tx, err := t.db.dbPool.Begin(ctx)
if err != nil {
return nil, er.ErrInternal
}
defer tx.Rollback(ctx)
var id int
err = tx.QueryRow(ctx, `INSERT INTO ticket (name, description, allocation) VALUES ($1, $2, $3) RETURNING id`, ticket.Name, ticket.Description, ticket.Allocation).Scan(&id)
if err != nil {
return nil, er.ErrInternal
}
ticket.Id = id
return &ticket, tx.Commit(ctx)
}
And that is my unit test for the handler.
func TestCreateTicketOptionHandler(t *testing.T) {
caseExpected, _ := json.Marshal(&ticket.Ticket{Id: 1, Name: "baris", Description: "test-desc", Allocation: 10})
srv := NewServer()
// expected := [][]byte{
// _, _ = json.Marshal(&ticket.Ticket{Id: 1, Name: "baris", Description: "test-desc", Allocation: 20}),
// // json.Marshal(&ticket.Ticket{Id: 1, Name: "baris", Description: "test-desc", Allocation: 20})
// }
tt := []struct {
name string
entry *ticket.Ticket
want []byte
code int
}{
{
"valid",
&ticket.Ticket{Name: "baris", Description: "test-desc", Allocation: 10},
caseExpected,
http.StatusOK,
},
}
var buf bytes.Buffer
for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
json.NewEncoder(&buf).Encode(tc.entry)
req, err := http.NewRequest(http.MethodPost, "/ticket_options", &buf)
log.Println("1")
if err != nil {
log.Println("2")
t.Fatalf("could not create request: %v", err)
}
log.Println("3")
rec := httptest.NewRecorder()
log.Println("4")
srv.handleCreateTicketOption(rec, req)
log.Println("5")
if rec.Code != tc.code {
t.Fatalf("got status %d, want %v", rec.Code, tc.code)
}
log.Println("6")
if reflect.DeepEqual(rec.Body.Bytes(), tc.want) {
log.Println("7")
t.Fatalf("NAME:%v, got %v, want %v", tc.name, rec.Body.Bytes(), tc.want)
}
})
}
}
I did research about mocking pgx about most of them were testing the logic part not through the handler. I want to write unit test for both handler and logic itself seperately. However, the unit test I've written for the handler panics as below
github.com/bariis/gowit-case-study/psql.(*TicketService).CreateTicketOption(0xc000061348, {0x1485058, 0xc0000260c0}, {0x0, {0xc000026dd0, 0x5}, {0xc000026dd5, 0x9}, 0xa})
/Users/barisertas/workspace/gowit-case-study/psql/ticket.go:24 +0x125
github.com/bariis/gowit-case-study/http.(*Server).handleCreateTicketOption(0xc000061340, {0x1484bf0, 0xc000153280}, 0xc00018e000)
/Users/barisertas/workspace/gowit-case-study/http/ticket.go:77 +0x10b
github.com/bariis/gowit-case-study/http.TestCreateTicketOptionHandler.func2(0xc000119860)
/Users/barisertas/workspace/gowit-case-study/http/ticket_test.go:80 +0x305
psql/ticket.go:24: tx, err := t.db.dbPool.Begin(ctx)
http/ticket.go:77: ticket, err := s.TicketService.CreateTicketOption(r.Context(), t)
http/ticket_test.go:80: srv.handleCreateTicketOption(rec, req)
How can I mock this type of code?
Create an interface which has the required DB functions
Your DB handler implements this interface. You use the handler in actual execution
Create a mock handler using testify/mock and use this in place of DB handler in test cases
From what I can read, you have the following structure:
type Server struct {
TicketService ticket.Service
}
type TicketService struct {
db *sql.Db // ..or similar
}
func (ts *TicketService) CreateTicketOption(...)
The trick to mock this is by ensuring ticket.Service is an interface instead of a struct.
Like this:
type TicketService interface {
CreateTicketOption(ctx context.Context, ticket ticket.Ticket) (*ticket.Ticket, error) {
}
By doing this, your Server expects a TicketService interface.
Then you could do this:
type postgresTicketService struct {
db *sql.Db
}
func (pst *postgresTicketService) CreateTicketOption(...)...
Which means that the postgresTicketService satisfies the requirements to be passed as a ticket.Service to the Server.
This also means that you can do this:
type mockTicketService struct {
}
func (mts *mockTicketService) CreateTicketOption(...)...
This way you decouple the Server from the actual implementation, and you could just init the Server with the mockTicketService when testing and postgresTicketService when deploying.
I have a function which gets all PDF files in a directory and returns the files if there are some.
func GetPdfFiles(path string) ([]string, error) {
var files []string
err := filepath.Walk(path, func(path string, info fs.FileInfo, err error) error {
if strings.HasSuffix(path, ".pdf") {
files = append(files, path)
}
return nil
})
if err != nil {
return nil, err
}
if files == nil {
return nil, errors.New("No PdfFiles found.")
}
return files, nil
}
My Test function gets the error: nil, from the filepath.Walk function which requires a anonymous function that returns an error, but it should get the error from the if statements like in the case of the second testcase with no files it should return errors.New("No PdfFiles found.").
How can i test it correctly.
func TestGetPdfFiles(t *testing.T) {
type args struct {
path string
}
cwd, err := os.Getwd()
if err != nil {
log.Fatal(err)
}
tests := []struct {
name string
args args
want []string
wantErr bool
}{
{name: "2PdfFiles", args: args{path: fmt.Sprintf("%s/testData/", cwd)}, want: []string{fmt.Sprintf("%s/testData/test-1.pdf", cwd), fmt.Sprintf("%s/testData/test-2.pdf", cwd)}, wantErr: false},
{name: "noPdfFiles", args: args{path: fmt.Sprintf("%s", cwd)}, want: nil, wantErr: true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := GetPdfFiles(tt.args.path)
if (err != nil) != tt.wantErr {
t.Errorf("GetPdfFiles() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("GetPdfFiles() got = %v, want %v", got, tt.want)
}
})
}
}
You use dependency injection, and modify your function to accept an implementation of fs.FS. That lets your tests pass it a mock file system.
https://bitfieldconsulting.com/golang/filesystems
https://www.gopherguides.com/articles/golang-1.16-io-fs-improve-test-performance
Or, perhaps simpler for your use case, modify your GetPdfFiles() to accept a directory walker function with the same signature as path.WalkDir():
package main
import (
"io/fs"
"path"
"path/filepath"
"strings"
)
func GetPdfFiles(root string) ([]string, error) {
return GetPdfFilesWithWalker(root, filepath.WalkDir)
}
type DirectoryWalker = func(string, fs.WalkDirFunc) error
func GetPdfFilesWithWalker(root string, walk DirectoryWalker) (fns []string, err error) {
collectPdfFiles := func(fn string, info fs.DirEntry, err error) error {
if ext := strings.ToLower(path.Ext(fn)); ext == ".pdf" {
fns = append(fns, fn)
}
return nil
}
err = walk(root, collectPdfFiles)
return fns, err
}
Now your GetPdfFiles() is a do-nothing wrapper that injects the default implementation (from path/filepath), and the core is in GetPdfFilesWithWalker(), against which you write your tests, passing in a suitable mock.
you can even construct a mock that will return errors, so you can test your error handling.
Your mock directory walker can be as simple as something like this (especially since you only use the path passed to the callback:
func MockDirectoryWalker(root string, visit fs.WalkDirFunc) (err error) {
paths := [][]string{
{root, "a"},
{root, "a", "a.pdf"},
{root, "a", "b.txt"},
{root,"a", "b"},
{root, "a", "b", "c.pdf"},
{root, "a", "b", "d.txt"},
}
for _, p := range paths {
fqn := path.Join(p...)
var di fs.DirEntry
visit(fqn, di, nil)
}
return err
}
How about using T.TempDir() for a unique empty directory every time. This will guarantee repeatable results. The testing package will also take care of cleaning that:
func TestGetPdfFiles(t *testing.T) {
type args struct {
path string
}
tests := []struct {
name string
args args
want []string
wantErr bool
}{
{name: "noPdfFiles", args: args{path: t.TempDir()}, want: nil, wantErr: true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := GetPdfFiles(tt.args.path)
if (err != nil) != tt.wantErr {
t.Errorf("GetPdfFiles() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("GetPdfFiles() got = %v, want %v", got, tt.want)
}
})
}
}
I guess, there isn't really a good way to do it.
so i removed the error checks and just return an empty array, it would've been too messy.
Also thanks to #Qasim Sarfraz for the T.TempDir idea.
func GetPdfFiles(path string) []string {
var files []string
filepath.Walk(path, func(path string, info fs.FileInfo, err error) error {
if strings.HasSuffix(path, ".pdf") {
files = append(files, path)
}
return nil
})
return files
}
func TestGetPdfFiles(t *testing.T) {
type args struct {
path string
}
cwd, err := os.Getwd()
if err != nil {
log.Fatal(err)
}
tests := []struct {
name string
args args
want []string
wantErr bool
}{
{name: "2PdfFiles", args: args{path: fmt.Sprintf("%s/testData/", cwd)}, want: []string{fmt.Sprintf("%s/testData/test-1.pdf", cwd), fmt.Sprintf("%s/testData/test-2.pdf", cwd)}},
{name: "noPdfFiles", args: args{path: t.TempDir()}, want: nil},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := GetPdfFiles(tt.args.path)
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("GetPdfFiles() got = %v, want %v", got, tt.want)
}
})
}
}
I use the following code which works ok.
This is working example
https://play.golang.org/p/wjvJtDNvJAQ
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
)
type requester interface {
HTTPRequest(c string, i string, mtd string, url string) (p []byte, e error)
}
type impl struct {
client *http.Client
}
// ----This is the function which I need to mock
func (s *ServiceInfo) wrapperFN() {
// Function 1 - get the values
v1, v2 := s.json.parseJson()
// call to http function
s.req.HTTPRequest(v1, v2, "POST", "http://www.mocky.io/v2/5c20eccc2e00005c001e0c84")
}
func (i impl) HTTPRequest(c string, ci string, mtd string, url string) (p []byte, e error) {
req, err := http.NewRequest(mtd, url, nil)
if err != nil {
return nil, err
}
req.SetBasicAuth(c, ci)
res, err := i.client.Do(req)
if err != nil {
return nil, err
}
token, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, err
}
defer res.Body.Close()
fmt.Println("success")
return token, nil
}
type parser interface {
parseJson() (string, string)
}
type jsonP struct {
data string
}
func (s jsonP) parseJson() (string, string) {
var result map[string]interface{}
json.Unmarshal([]byte(s.data), &result)
b := result["person"].(map[string]interface{})
for key, value := range b {
return key, value.(string)
}
return "", ""
}
type ServiceInfo struct {
req requester
json parser
}
// When in production pass in concrete implementations.
func NewServiceInfo(http requester, json parser) *ServiceInfo {
return &ServiceInfo{
req: http,
json: json,
}
}
func main() {
httpClient := http.Client{}
js := `{"person":{"p1":"username","p2":"password"},"customers":"10"}`
j := jsonP{data: js}
s := NewServiceInfo(impl{client: &httpClient}, j)
s.wrapperFN()
}
Now i want to test it wrapperFN , what I try I've changed the code to use interface , which works.
This is just example to give a point ( the real code much more complicated)
The problem that I dont understand how to mock function inside wrapperFN like parseJson() , in the real world warpperFN contains several function which I need to mock ,because just calling them in the test will provide error.
How it's best to mock function like parseJson() & HTTPRequest? and assume that inside wrapperFN there is additional functions which is not related...
I need to know if this is the best practice for testing function.
This is the test (which im not sure how to make it right)
package main
import (
"net/http"
"net/http/httptest"
"testing"
)
func TestServiceInfo_wrapperFN(t *testing.T) {
tests := []struct {
name string
s *ServiceInfo
}{
{
name: "wrapper test",
s: &ServiceInfo{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var testHandler http.Handler
srv := httptest.NewServer(testHandler)
defer srv.Close()
iReq := &impl{
client: srv.Client(),
}
v := &ServiceInfo{http: *iReq}
v.wrapperFN()
})
}
}
As far as I can tell I'm following structure needed for 'go test' flawlessly. I don't see a discrepancy from tests I could run in other packages. 'go build' works fine.
I'm getting
./HelloTemplate_test.go:3: imported and not used: "testing"
./HelloTemplate_test.go:5: undefined: Testing in Testing.T
What am I missing?
HelloTemplate.go
package templateprint
import "testing"
func TestRunTempl(t *Testing.T) {
sweaters := Inventory{"wool", 17}
tmpl := "{{.Count}} items are made of {{.Material}}"
err := RunTempl(tmpl, sweaters)
if err != nil {
t.Error("Template failed ")
}
}
HelloTemplate_test.go
package templateprint
import (
"os"
"text/template"
)
type Inventory struct {
Material string
Count uint
}
func RunTempl(templ string, inv Inventory) error {
tmpl, err := template.New("test").Parse(templ)
if err != nil {
return (err)
}
err = tmpl.Execute(os.Stdout, inv)
if err != nil {
return (err)
}
return nil
}
You are using an incorrect type in your test function:
// testing.T, not Testing.T
// T is a type defined in testing module
func TestRunTempl(t *testing.T) {
sweaters := Inventory{"wool", 17}
tmpl := "{{.Count}} items are made of {{.Material}}"
err := RunTempl(tmpl, sweaters)
if err != nil {
t.Error("Template failed ")
}
}
I have the following function:
func GetDataFromFile(path string) ([]byte, error) {
_, err := os.Stat(path)
if err != nil {
return nil, err
}
data, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
return data, nil
}
I want to do tests for functions ioutil.ReadFile and os.Stat(path) when they throw errors.
I know that I can create non-exist path for os.Stat(path), but how to test such kind functions without "workarounds" and guessing how functions are working?
Regards.
I agree with abhink here, I would not expect you to test this particular function. But in practice, similar situation happens often.
My best solution is to use a factory to create GetDataFromFile. In this case, you inject the dependencies.
main.go
package main
import (
"io/ioutil"
"os"
)
func getDataFromFileFactory(
stat func(filename string) (os.FileInfo, error),
readFile func(filename string) ([]byte, error),
) func(path string) ([]byte, error) {
return func(path string) ([]byte, error) {
_, err := stat(path)
if err != nil {
return nil, err
}
data, err := readFile(path)
if err != nil {
return nil, err
}
return data, nil
}
}
var GetDataFromFile = getDataFromFileFactory(os.Stat, ioutil.ReadFile)
func main() {}
main_test.go
package main
import (
"errors"
"os"
"testing"
)
func TestGetDataFromFile(t *testing.T) {
stat := func(filename string) (os.FileInfo, error) {
return nil, errors.New("err msg")
}
readfile := func(filename string) ([]byte, error) {
t.Error("should not call this function")
return nil, nil
}
getDataFromFile := getDataFromFileFactory(stat, readfile)
if _, err := getDataFromFile("foo"); err.Error() != "err msg" {
t.Error("expected an error to be thrown")
}
}
Is there any specific reason you want to test these library functions?
You should only concern yourself with testing your own code and logic. Libraries are tested by those who create and maintain them and you should use them fully expecting to perform as per their documentation. Any genuine deviation from expected behavior should be reported to the authors/maintainers of the library.
As for testing GetDataFromFile, it would be perfectly legitimate to test it by supplying incorrect path. For more specific errors you can also read up on the input parameters that would cause them and call the functions with those arguments.