I got an example from #volker about table driven test like following
But currently I miss what I should put in the real test, this test is using byte, currently im not sure what to put in the args and the expected []byte,
e.g. I want to check that in the file there is 2 new line and then application entry, how can I do it without the need to create the real file and parse it?
type Models struct {
name string
vtype string
contentType string
}
func setFile(file io.Writer, appStr Models) {
fmt.Fprint(file, "1.0")
fmt.Fprint(file, "Created-By: application generation process")
for _, mod := range appStr.Modules {
fmt.Fprint(file, "\n")
fmt.Fprint(file, "\n")
fmt.Fprint(file, appStr.vtype) //"userApp"
fmt.Fprint(file, "\n")
fmt.Fprint(file, appStr.name) //"applicationValue"
fmt.Fprint(file, "\n")
fmt.Fprint(file, appStr.contentType)//"ContentType"
}
}
func Test_setFile(t *testing.T) {
type args struct {
appStr models.App
}
var tests []struct {
name string
args args
expected []byte
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
b := &bytes.Buffer{}
setFile(b, tt.args.AppStr)
if !bytes.Equal(b.Bytes(), tt.expected) {
t.Error("somewhat bad happen")
}
})
}
}
I read and understand the following example but not for byte and file
https://medium.com/#virup/how-to-write-concise-tests-table-driven-tests-ed672c502ae4
If you are only checking for the static content at the beginning, then you really only need one test. It would look something like this:
func Test_setFile(t *testing.T) {
type args struct {
appStr models.App
}
var tests []struct {
name string
args args
expected []byte
}{
name: 'Test Static Content',
args: args{appStr: 'Some String'},
expected: []byte(fmt.Sprintf("%s%s%s", NEW_LINE, NEW_LINE, "Application")),
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
b := &bytes.Buffer{}
setFile(b, tt.args.AppStr)
if !bytes.Equal(b.Bytes(), tt.expected) {
t.Error("somewhat bad happen")
}
})
}
}
Although, since you only have one case for this test, there really isn't a need to use table driven tests here. You could clean it up to look something like this:
func Test_setFile(t *testing.T) {
b := &bytes.Buffer{}
setFile(b, 'Some String')
want := []byte(fmt.Sprintf("%s%s%s", NEW_LINE, NEW_LINE, "Application"))
got := b.Bytes()
if !bytes.Equal(want, got) {
t.Errorf("want: %s got: %s", want, got)
}
}
Related
This question already has an answer here:
Table Testing Go Generics
(1 answer)
Closed 4 months ago.
We were brainstorming possible ways of testing generic functions using table driven tests.
It seems pretty complicated at the first glance.
What we wanted to achieve is to have a field in the test table struct that can be of whatever type that is accepted by the generic function. It seems however, that you can't use any interface for that.
We came up with the following solution:
Example function:
func PtrTo[T any](t T) *T {
return &t
}
Example test:
func TestPtrTo(t *testing.T) {
string1 := "abcd"
trueVar := true
testCases := []struct {
testDescription string
input interface{}
expectedOutput interface{}
}{
{
testDescription: "string",
input: string1,
expectedOutput: &string1,
},
{
testDescription: "bool",
input: trueVar,
expectedOutput: &trueVar,
},
}
for _, testCase := range testCases {
t.Run(testCase.testDescription, func(t *testing.T) {
switch concreteTypeInput := testCase.input.(type) {
case string:
output := PtrTo(concreteTypeInput)
assert.Equal(t, testCase.expectedOutput, output)
case bool:
output := PtrTo(concreteTypeInput)
assert.Equal(t, testCase.expectedOutput, output)
default:
t.Error("Unexpected type. Please add the type to the switch case")
}
})
}
}
It doesn't really feel optimal, though.
What do you think of this solution?
Do you see any other alternatives?
A templated helper function could give you the semantics I think you're looking for:
package main
import (
"testing"
"github.com/google/go-cmp/cmp"
)
type testCase[T any] struct {
desc string
in T
want *T
}
func ptrToTest[T any](t *testing.T, tc testCase[T]) {
t.Helper()
t.Run(tc.desc, func(t *testing.T) {
got := PtrTo(tc.in)
if diff := cmp.Diff(tc.want, got); diff != "" {
t.Fatalf("got %v, want %v", got, tc.want)
}
})
}
func TestPtrTo(t *testing.T) {
string1 := "abcd"
ptrToTest(t, testCase[string]{
desc: "string",
in: string1,
want: &string1,
})
trueVar := true
ptrToTest(t, testCase[bool]{
desc: "bool",
in: trueVar,
want: &trueVar,
})
}
That being said, I agree with #blackgreen that the PtrTo function is too trivial for a test like this to be meaningful. Hopefully this is useful for more complicated logic!
I use Testify to create a unit test for my golang app. I need to create a unit test for this function where it calls a variadic function (function with trailing arguments). I encountered an error when I test it. I'm actually not sure if the error is because of the trailing argument itself or not, but I feel like there's something wrong with the mock.
// svc/callThisFunction.go
// data type of args is []sqkit.SelectOption
func CallThisFunction(ctx context.Context, args ...sqkit.SelectFunctiom) (result string, err error) {
return result, nil
}
// svc/functionToTest.go
// This is the function that I wanna test
func FunctionToTest(ctx context.Context, id int64) (result string, err error) {
args := []sqkit.SelectOption{
sqkit.Where{
fmt.Sprintf("id = %d", id),
},
}
newResult, err := callThisFunctionService.CallThisFunction(ctx, args)
if err != nil {
return newResult, err
}
return newResult, nil
}
// svc/functionToTest_test.go
func Test_FunctionToTest(t *testing.T) {
testCase := []struct {
name string
id int64
onCallThisFunctionMock func(callThisFunctionSvc *mocks.CallThisFunctionSvc)
expectedResult string
wantError bool
expectedError error
}{
{
name: "Success",
id: 1,
onCallThisFunctionMock: func(callThisFunctionSvc *mocks.CallThisFunctionSvc) {
// NOTE: I've created 2 different versions (used separately, not at the same), using mock.Anything() and using actual arguments
// Both of these give the same errors
// Using actual arguments
args := []sqkit.SelectOption{
sqkit.Where{
fmt.Sprintf("id = %d", 1},
},
}
callThisFunctionSvc.On("CallThisFunction", context.Background(), args).Return("Success", nil)
// Using mock.Anything
callThisFunctionSvc.On("CallThisFunction", context.Background(), mock.Anything).Return("Success", nil)
}
}
}
for _, tc := range testCases {
var callThisFunctionSvc = new(mocks.CallThisFunctionSvc)
tc.onCallThisFunctionMock(callThisFunctionSvc)
svc := &svc.FunctionToTest{
CallThisFunction: callThisFunctionSvc,
}
actualResult, actualError := svc.FunctionToTest(context.Background(), tc.id)
if tc.wantEror {
require.Error(t, actualError, tc.expectedError)
} else {
require.NoError(t, actualError)
}
require.Equal(t, tc.expectedResult, actualResult)
}
}
This is the error it gives
=== RUN Test_GenerateDocument
--- FAIL: Test_GenerateDocument (0.00s)
panic:
assert: mock: I don't know what to return because the method call was unexpected.
Either do Mock.On("CallThisFunction").Return(...) first, or remove the GetTemplates() call.
This method was unexpected:
CallThisFunction(*context.emptyCtx,sqkit.Where)
0: (*context.emptyCtx)(0xc0000a4010)
1: sqkit.Where{"id = 1"}
Usually, when I encountered an error like this, it's because I haven't defined the return values of the function calls inside the function I wanna test. But this time I've created it, but it somehow can't read the return. Any idea why?
The error indicates you called CallThisFuncion with params (context.Context, sqkit.Where), but your example is using and setting the expectation for (context.Context, []sqkit.Option). The example with mock.Anything should work, but I believe it's failing because of the context. You'll need to set the expectation with the same context you're passing down. If FunctionToTest is going to be altering the context, I believe you'll need to use mock.Anything instead.
func Test_FunctionToTest(t *testing.T) {
testCase := []struct {
name string
id int64
onCallThisFunctionMock func(context.Context, *mocks.CallThisFunctionSvc)
expectedResult string
wantError bool
expectedError error
}{
{
name: "Success",
id: 1,
onCallThisFunctionMock: func(ctx context.Context, callThisFunctionSvc *mocks.CallThisFunctionSvc) {
args := []sqkit.SelectOption{
sqkit.Where{
fmt.Sprintf("id = %d", 1},
},
}
callThisFunctionSvc.On("CallThisFunction", ctx, args).Return("Success", nil)
}
}
}
for _, tc := range testCases {
var callThisFunctionSvc = new(mocks.CallThisFunctionSvc)
var ctx = context.Background()
tc.onCallThisFunctionMock(ctx, callThisFunctionSvc)
svc := &svc.FunctionToTest{
CallThisFunction: callThisFunctionSvc,
}
actualResult, actualError := svc.FunctionToTest(ctx, tc.id)
if tc.wantEror {
require.Error(t, actualError, tc.expectedError)
} else {
require.NoError(t, actualError)
}
require.Equal(t, tc.expectedResult, actualResult)
}
}
If you want to ensure a context.Context was passed as the first parameter but don't care what context, you could use AnythingOfType.
callThisFunctionSvc.On("CallThisFunction", mock.AnythingOfType("context.Context"), args).Return("Success", nil)
I am writing unit test in golang by https://github.com/stretchr/testify
Suppose I have a method below,
func DoSomething(result interface{}) error {
// write some data to result
return nil
}
so the caller can call DoSomething as following
result := &SomeStruct{}
err := DoSomething(result)
if err != nil {
fmt.Println(err)
} else {
fmt.Println("The result is", result)
}
Now I know how to use testify or some other mocking tools to mock the returns value (it's err here) by something like
mockObj.On("DoSomething", mock.Anything).Return(errors.New("mock error"))
My question is "how do i mock the result argument" in this kind of scenario?
Since result is not a return value but a argument, the caller calls it by passing a pointer of a struct, and the function modify it.
You can use the (*Call).Run method:
Run sets a handler to be called before returning. It can be used when
mocking a method (such as an unmarshaler) that takes a pointer to a
struct and sets properties in such struct
Example:
mockObj.On("Unmarshal", mock.AnythingOfType("*map[string]interface{}")).Return().Run(func(args Arguments) {
arg := args.Get(0).(*map[string]interface{})
arg["foo"] = "bar"
})
As #bikbah said, here is an example:
services/message.go:
type messageService struct {
HttpClient http.Client
BaseURL string
}
func (m *messageService) MarkAllMessages(accesstoken string) []*model.MarkedMessage {
endpoint := m.BaseURL + "/message/mark_all"
var res model.MarkAllMessagesResponse
if err := m.HttpClient.Post(endpoint, &MarkAllMessagesRequestPayload{Accesstoken: accesstoken}, &res); err != nil {
fmt.Println(err)
return res.MarkedMsgs
}
return res.MarkedMsgs
}
We passes res to the m.HttpClient.Post method. In this method, the res will be populated with json.unmarshal method.
mocks/http.go:
package mocks
import (
"io"
"github.com/stretchr/testify/mock"
)
type MockedHttp struct {
mock.Mock
}
func (m *MockedHttp) Get(url string, data interface{}) error {
args := m.Called(url, data)
return args.Error(0)
}
func (m *MockedHttp) Post(url string, body interface{}, data interface{}) error {
args := m.Called(url, body, data)
return args.Error(0)
}
services/message_test.go:
package services_test
import (
"errors"
"reflect"
"strconv"
"testing"
"github.com/stretchr/testify/mock"
"github.com/mrdulin/gqlgen-cnode/graph/model"
"github.com/mrdulin/gqlgen-cnode/services"
"github.com/mrdulin/gqlgen-cnode/mocks"
)
const (
baseURL string = "http://localhost/api/v1"
accesstoken string = "123"
)
func TestMessageService_MarkAllMessages(t *testing.T) {
t.Run("should mark all messaages", func(t *testing.T) {
testHttp := new(mocks.MockedHttp)
var res model.MarkAllMessagesResponse
var markedMsgs []*model.MarkedMessage
for i := 1; i <= 3; i++ {
markedMsgs = append(markedMsgs, &model.MarkedMessage{ID: strconv.Itoa(i)})
}
postBody := services.MarkAllMessagesRequestPayload{Accesstoken: accesstoken}
testHttp.On("Post", baseURL+"/message/mark_all", &postBody, &res).Return(nil).Run(func(args mock.Arguments) {
arg := args.Get(2).(*model.MarkAllMessagesResponse)
arg.MarkedMsgs = markedMsgs
})
service := services.NewMessageService(testHttp, baseURL)
got := service.MarkAllMessages(accesstoken)
want := markedMsgs
testHttp.AssertExpectations(t)
if !reflect.DeepEqual(got, want) {
t.Errorf("got wrong return value. got: %#v, want: %#v", got, want)
}
})
t.Run("should print error and return empty slice", func(t *testing.T) {
var res model.MarkAllMessagesResponse
testHttp := new(mocks.MockedHttp)
postBody := services.MarkAllMessagesRequestPayload{Accesstoken: accesstoken}
testHttp.On("Post", baseURL+"/message/mark_all", &postBody, &res).Return(errors.New("network"))
service := services.NewMessageService(testHttp, baseURL)
got := service.MarkAllMessages(accesstoken)
var want []*model.MarkedMessage
testHttp.AssertExpectations(t)
if !reflect.DeepEqual(got, want) {
t.Errorf("got wrong return value. got: %#v, want: %#v", got, want)
}
})
}
In the unit test case, we populated the res in #Call.Run method and assigned the return value(res.MarkedMsgs) of service.MarkAllMessages(accesstoken) to got variable.
unit test result and coverage:
=== RUN TestMessageService_MarkAllMessages
--- PASS: TestMessageService_MarkAllMessages (0.00s)
=== RUN TestMessageService_MarkAllMessages/should_mark_all_messaages
TestMessageService_MarkAllMessages/should_mark_all_messaages: message_test.go:39: PASS: Post(string,*services.MarkAllMessagesRequestPayload,*model.MarkAllMessagesResponse)
--- PASS: TestMessageService_MarkAllMessages/should_mark_all_messaages (0.00s)
=== RUN TestMessageService_MarkAllMessages/should_print_error_and_return_empty_slice
network
TestMessageService_MarkAllMessages/should_print_error_and_return_empty_slice: message_test.go:53: PASS: Post(string,*services.MarkAllMessagesRequestPayload,*model.MarkAllMessagesResponse)
--- PASS: TestMessageService_MarkAllMessages/should_print_error_and_return_empty_slice (0.00s)
PASS
coverage: 5.6% of statements in ../../gqlgen-cnode/...
Process finished with exit code 0
I highly recommend to get familiar with the gomock framework and develop towards interfaces. What you need would look something like this.
// SetArg does the job
myObj.EXPECT().DoSomething(gomock.Any()).SetArg(0, <value you want to r eturn>).Return(nil)
https://github.com/golang/mock#building-mocks
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()
})
}
}
I have the following Golang code:
func getConfigFile() string {
var configFile string
flag.StringVar(&configFile, "config", "", "File containing configuration")
flag.Parse()
return configFile
}
This function is used elsewhere in my code, and I'd like to unit test what happens here when the user provides different values for the config argument (the config file name is used else where).
Is there a way to tell the flag package to return different values for the config argument while under test?
I have found that for testing custom flags is better to create a custom flag set, in that way I can fully test the flags, including the -h option without exiting the tests. hope the attached code could give you and idea of how you could implement test on your code:
package main
import (
"flag"
"fmt"
"os"
"reflect"
"testing"
)
// Test Helper
func expect(t *testing.T, a interface{}, b interface{}) {
if a != b {
t.Errorf("Expected: %v (type %v) Got: %v (type %v)", a, reflect.TypeOf(a), b, reflect.TypeOf(b))
}
}
type Flags struct {
ConfigFile string
}
func (self *Flags) Parse(fs *flag.FlagSet) (*Flags, error) {
fs.StringVar(&self.ConfigFile, "config", "", "File containing configuration")
err := fs.Parse(os.Args[1:])
if err != nil {
return nil, err
}
return self, nil
}
func main() {
fs := flag.NewFlagSet("test", flag.ContinueOnError)
parser := Flags{}
flags, err := parser.Parse(fs)
if err != nil {
panic(err)
}
fmt.Println(flags)
}
func TestFlags(t *testing.T) {
oldArgs := os.Args
defer func() { os.Args = oldArgs }()
var flagTest = []struct {
flag []string
name string
expected interface{}
}{
{[]string{"cmd", "-config", "config.yaml"}, "ConfigFile", "config.yaml"},
{[]string{"cmd", "-config", "config.json"}, "ConfigFile", "config.json"},
{[]string{"cmd", "-v"}, "Version", true},
}
for _, f := range flagTest {
os.Args = f.flag
p := &Flags{}
fs := flag.NewFlagSet("test", flag.ContinueOnError)
flags, err := p.Parse(fs)
if err != nil {
t.Error(err)
}
refValue := reflect.ValueOf(flags).Elem().FieldByName(f.name)
switch refValue.Kind() {
case reflect.Bool:
expect(t, f.expected, refValue.Bool())
case reflect.String:
expect(t, f.expected, refValue.String())
}
}
}
I put it also here: https://play.golang.org/p/h1nok1UMLA hope it can give you an idea.
If you change it like the code below, go test will fail but go test -config testconfig will pass. Not that we don't need to call flag.Parse() in the init() since it is be called by the testing package (as Rob Pike mentions in https://groups.google.com/d/msg/golang-nuts/uSFM8jG7yn4/PIQfEWOZx4EJ).
package main
import (
"flag"
"testing"
)
var configFile = flag.String("config", "", "File containing configuration")
func getConfigFile() string {
return *configFile
}
func TestConfig(t *testing.T) {
want := "testconfig"
if s := getConfigFile(); s != want {
t.Errorf("Got %s, want %s", s, want)
}
}
Test runs:
$ go test
--- FAIL: TestConfig (0.00s)
flag_test.go:17: Got , want testconfig
FAIL
exit status 1
FAIL github.com/dmitris/soflagtest 0.013s
$ go test -config testconfig
PASS
ok github.com/dmitris/soflagtest 0.012s
You can also use
var configFile string declaration and an init() function to assign the flag value to the variable:
func init() {
flag.StringVar(&configFile, "config", "", "File containing configuration")
}
(then no pointer dereferencing in getConfigFile since configFile is a string)