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!
Related
I'm validating my config file using Go's validator package
One of the fields(createTime) from the config file I'm reading as a string, and want to make sure that it is a valid time duration:
type Config struct{
...
CreateTime string `yaml:"createTime" validate:"duration,required"`
...
}
So, I have written a custom validation function as follows:
import (
"time"
"github.com/go-playground/validator/v10"
)
// isValidDuration is a custom validation function, and it will check if the given string is a valid time duration
func isValidDuration(fl validator.FieldLevel) bool {
if _, err := time.ParseDuration(fl.Field().String()); err != nil {
return false
}
return true
}
Which I'm using for validation as follows:
func (configObject *Config) Validate() error {
// Validate configurations
validate := validator.New()
if err := validate.RegisterValidation("duration", isValidDuration); err != nil {
return err
}
return validate.Struct(configObject)
}
The custom validator is working fine and I want to write a unit test for isValidDuration function. Following is the unit test boilerplate generated by IDE:
import (
"testing"
"github.com/go-playground/validator/v10"
)
func Test_isValidDuration(t *testing.T) {
type args struct {
fl validator.FieldLevel
}
var tests = []struct {
name string
args args
want bool
}{
// TODO: Add test cases.
{name: "positive", args: ???????, want: true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := isValidDuration(tt.args.fl); got != tt.want {
t.Errorf("isValidDuration() = %v, want %v", got, tt.want)
}
})
}
}
I'm new to Go and not sure what to pass in the args field of the testcase above. How do I create a struct containing one validator.FieldLevel field?
Ideally, I would like to pass something like "10m" as args and since it is a valid time duration, would expect the output of isValidDuration as true since "10m" is a valid duration.
I'm trying this: {name: "positive", args: struct{ fl validator.FieldLevel }{fl: "10m"}, want: true} but getting this ereor: '"10m"' (type string) cannot be represented by the type validator.FieldLevel
How do I create a validator.FieldLevel variable with value equivalent to "10m"? Could someone please help me out?
problem1
A language semantic problem, fl is not the type String.
{
name: "positive",
args: args{
fl: validator.FieldLevel{
// ....
},
},
want: true,
},
problem2
How to use validator.FieldLevel.
In code base, we can see FieldLevel is a interface, and you need create struct validate, which is not exported and not supposed to be used by user.
type FieldLevel interface
// ...
var _ FieldLevel = new(validate)
answer
So, you'd best to write your UT like UT of validator package. Don't use the code from your IDE!
// have a look at this UT
// github.com/go-playground/validator/v10#v10.10.0/validator_test.go
func TestKeysCustomValidation(t *testing.T) {
I am trying to check if the returned data equals the expectation
Here is my function:
func extractData(payload string) (interface{}, time.Time, error) {
eventTime := gjson.Get(payload, "data.eventDateTime").String()
dateTime, err := time.Parse("2006-01-02T15:04:05-07:00", eventTime)
if err != nil {
return nil, time.Time{}, fmt.Errorf("Date Time Error: %w", err)
}
body := data.Body{
EventTime: dateTime,
}
return body, dateTime, nil
}
This is the unit test I wrote for it:
func TestExtractData(t *testing.T) {
tests := []struct {
Name string
Payload string
ExpectedOutput interface{}
}{
{
Name: "test-2",
Payload: "{\"data\":\"2020-11-02T10:44:48+01:00\"}",
ExpectedOutput: data.Body{
EventTime: time.Date(2020, 11, 2, 10, 44, 48, 0, time.FixedZone("CET", 3600)),
},
},
}
for _, tt := range tests {
t.Run(tt.Name, func(t *testing.T) {
data, _, _ := extractData(tt.Payload)
assert.Equal(t, tt.ExpectedOutput, data)
})
}
}
The test is failing, it outputs:
{ 2020-11-02 10:44:48 +0100 CET} does not equal { 2020-11-02 10:44:48 +0100 CET}
I believe the problem is in TimeZone, but I struggles to write the test code.
I don't want to test it using time.Equal, because my data.Body does not always contains the eventTime element.
I tried (by temporary use type not interface):
if !tt.ExpectedOutput.EventTime.Equal(data.EventTime) {
//throw error
}
and it works.
I also i tried:
if !reflect.DeepEqual(tt.ExpectedOutput.EventTime, data.EventTime) {
t.Errorf("extractData() output = %v, want = %v",data,tt.ExpectedOutput)
}
The code above fails.
You're partially correct that the time zone can make two time.Time instances representing the same time fail equality. It can also be that because since go 1.9, time.Time also includes wall time to make all comparisons safe, time.Time literals such as your ExpectedOutput field will never contain this wall time, so are "different".
Your best solution is probably to compare the time using assert.WithinDuration() (I assume you are using github.com/stretchr/testify/assert?):
package kata
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestTime(t *testing.T) {
format := "2006-01-02 15:04:05.999999999 -0700 MST"
time1 := time.Now()
time2, err := time.Parse(format, time1.Format(format))
assert.NoError(t, err)
t.Log(time1)
t.Log(time2)
assert.Equal(t, time1, time2) // fails
assert.WithinDuration(t, time1, time2, 0) // passes
}
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 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)
}
}
I have next struct.
package logger
import "fmt"
type IPrinter interface {
Print(value string)
}
type ConsolePrinter struct{}
func (cp *ConsolePrinter) Print(value string) {
fmt.Printf("this is value: %s", value)
}
Test coverage says I need to test that ConsolePrinter Print method.
How can I cover this method?
Thanks.
Following comment that #icza wrote, I've written test below.
func TestPrint(t *testing.T) {
rescueStdout := os.Stdout
r, w, _ := os.Pipe()
os.Stdout = w
cp := &ConsolePrinter{}
cp.Print("test")
w.Close()
out, _ := ioutil.ReadAll(r)
os.Stdout = rescueStdout
if string(out) != "this is value: test" {
t.Errorf("Expected %s, got %s", "this is value: test", out)
}
}
I've found example in question What is the best way to convert byte array to string?.
Use Examples to convey the usage of a function.
Don't fret over 100% test coverage, especially for simple straightforward functions.
func ExampleHello() {
fmt.Println("hello")
// Output: hello
}
The additional benefit is that examples are outputted in a generated doc with go doc tool.
I would recommend to create new instance of the logger, which would behave exactly as fmt methods for printing data to console. Also, you can configure it with additional features like showing the filename, date and etc. Such custom logger can be passed as a parameter to your service/instance factory method. That would make it really easy to mock and test.
Your code
type Logs interface {
Println(v ...interface{})
}
type InstanceToTest struct {
log Logs
}
func InstanceToTestFactory(logger Logs) *InstanceToTest {
return &InstanceToTest{logger}
}
func (i *InstanceToTest) SomeMethod(a string) {
i.log.Println(a)
}
Create a mock for logger
type LoggerMock struct {
CalledPrintln []interface{}
}
func (l *LoggerMock) Println(v ...interface{}) {
l.CalledPrintln = append(CalledPrintln, v)
}
And in your test
func TestInstanceToTestSomeMethod(t *testing.T) {
l := &LoggerMock{}
i := InstanceToTestFactory(l)
a := "Test"
i.SomeMethod(a)
if len(l.CalledPrintln) == 0 || l.CalledPrintln[0] != a {
t.Error("Not called")
}
}