TL;DR
I'm wondering how to organise test functions using mocks for GoLang drier & easier to follow.
Function
I have a template function that is very similar to ACF's getField:
- It take's in a string (field name) & the post ID.
- It checks if the ID exists and the obtains the post by ID (the
f.store.Posts
belongs to thefields
struct) - If the post doesn't exist or if the fields could not be unmarshalled it will return an empty string.
func (f *fields) getField(field string, id ...int) interface{} {
fields := f.fields
if len(id) > 0 {
post, err := f.store.Posts.GetById(id[0])
if err != nil {
return ""
}
var m map[string]interface{}
if err := json.Unmarshal(*post.Fields, &m); err != nil {
return ""
}
fields = m
}
val, found := fields[field]
if !found {
return ""
}
return val
}
Testing
From the function, presumably I need to test if, The post exists, the json could be unmarshalle or if the field exists.
I have started to use github.com/golang/mock/gomock but am finding it confusing!
In each of the test functions below I create a new controller and mockPost, add assigning the fields Post
to it.
func TestGetField(t *testing.T) {
f, err := helper(`{"text": "content"}`)
if err != nil {
t.Error(err)
}
if field := f.getField("text"); field == "" {
t.Errorf(test.Format("content", nil))
}
if field := f.getField("wrongval"); field != "" {
t.Errorf(test.Format("", field))
}
}
func TestGetField_Post(t *testing.T) {
f, err := helper("{}")
if err != nil {
t.Error(err)
}
controller := gomock.NewController(t)
defer controller.Finish()
data := []byte(`{"posttext": "postcontent"}`)
mockPost := domain.Post{
Id: 2,
Fields: (*json.RawMessage)(&data),
}
posts := mocks.NewMockPostsRepository(controller)
f.store.Posts = posts
posts.EXPECT().GetById(2).Return(mockPost, nil)
field := f.getField("posttext", 2)
if field != "postcontent" {
t.Errorf(test.Format("postcontent", field))
}
}
func TestGetField_No_Post(t *testing.T) {
f, err := helper("{}")
if err != nil {
t.Error(err)
}
controller := gomock.NewController(t)
defer controller.Finish()
posts := mocks.NewMockPostsRepository(controller)
var mockErr = fmt.Errorf("No post")
posts.EXPECT().GetById(gomock.Any()).Return(domain.Post{}, mockErr)
f.store.Posts = posts
field := f.getField("text", 1)
if field != "" {
t.Errorf(test.Format("", field))
}
}
func TestGetField_Invalid_JSON(t *testing.T) {
f, err := helper("{}")
if err != nil {
t.Error(err)
}
controller := gomock.NewController(t)
defer controller.Finish()
data := []byte(`"text "content"`)
mockPost := domain.Post{
Id: 1,
Fields: (*json.RawMessage)(&data),
}
posts := mocks.NewMockPostsRepository(controller)
posts.EXPECT().GetById(1).Return(mockPost, nil)
f.store.Posts = posts
field := f.getField("text", 1)
if field != "" {
t.Errorf(test.Format("", field))
}
}
Feedback
- Without using the mock package, how would you approach this?
- Are there any areas for improvement with the current codebase?
- Is there away to consolidate any of the testing methods? It seems quite long winded for such a short function
(interface{}, error)
so that your errors can have qualities (and so empty string does not have special meaning, though granted, your posts are probably never empty) \$\endgroup\$