I'm pretty new with Go, and I'm coming from OOP languages. Now the concept seems quite different in go of interfaces and classes.
I was wondering how mocking would work in case of testing. The confusion I'm having is whether ok to use struct as a classes and if the approach below is how you suppose to do? Assuming that DefaultArticlesRepository would be for real data and MockArticlesRepository for mocking it.
type ArticlesRepository interface {
GetArticleSections() []ArticleSectionResponse
}
type DefaultArticlesRepository struct{}
type MockArticlesRepository struct{}
func (repository DefaultArticlesRepository) GetArticleSections() []ArticleSectionResponse {
return []ArticleSectionResponse{
{
Title: "Default response",
Tag: "Default Tag",
},
}
}
func (repository MockArticlesRepository) GetArticleSections() []ArticleSectionResponse {
return []ArticleSectionResponse{
{
Title: "Mock response",
Tag: "Mock Tag",
},
}
}
func ArticleSectionsProvider(v ArticlesRepository) ArticlesRepository {
return v
}
func TestFoo(t *testing.T) {
realProvider := ArticleSectionsProvider(DefaultArticlesRepository{})
mockProvider := ArticleSectionsProvider(MockArticlesRepository{})
assert.Equal(t, realProvider.GetArticleSections(), []ArticleSectionResponse{
{
Title: "Default response",
Tag: "Default Tag",
},
})
assert.Equal(t, mockProvider.GetArticleSections(), []ArticleSectionResponse{
{
Title: "Mock response",
Tag: "Mock Tag",
},
})
}
Firstly, there is no need to use any external mocking library like:
All you really is need is just an interface and some piece of code using standard library to run all your tests in parallel.
Check this real world example below instead of next "calculator testing example":
├── api
│ ├── storage.go
│ ├── server.go
│ └── server_test.go
└── main.go
api/storage.go
package api
import "database/sql"
type storage struct {
// any database driver of your choice...
pool *sql.DB
}
func NewStorage(p *sql.DB) *storage {
return &storage{pool: p}
}
func (s *storage) Find() (string, error) {
// use database driver to find from storage...
return `{ "id": "123" }`, nil
}
func (s *storage) Add(v string) error {
// use database driver to add to storage...
return nil
}
api/server.go
package api
import (
"fmt"
"net/http"
)
type Storage interface {
Find() (string, error)
Add(string) error
}
type server struct {
storage Storage
}
func NewServer(s Storage) *server {
return &server{storage: s}
}
func (s *server) Find(w http.ResponseWriter, r *http.Request) {
response, err := s.storage.Find()
if err != nil {
w.WriteHeader(http.StatusNotFound)
fmt.Fprint(w, `{ "message": "not found" }`)
return
}
fmt.Fprint(w, response)
}
func (s *server) Add(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query()
name := query.Get("name")
if err := s.storage.Add(name); err != nil {
w.WriteHeader(http.StatusNotFound)
fmt.Fprint(w, `{ "message": "not found" }`)
return
}
fmt.Fprint(w, `{ "message": "success" }`)
}
api/server_test.go
package api
import (
"errors"
"net/http"
"net/http/httptest"
"testing"
)
type mock struct {
find func() (string, error)
add func(string) error
}
func (m *mock) Find() (string, error) { return m.find() }
func (m *mock) Add(v string) error { return m.add(v) }
func TestFindOk(t *testing.T) {
t.Parallel()
// Arrange
expectedBody := `{ "message": "ok" }`
expectedStatus := http.StatusOK
m := &mock{find: func() (string, error) { return expectedBody, nil }}
server := NewServer(m)
recorder := httptest.NewRecorder()
// Act
server.Find(recorder, &http.Request{})
// Assert
if recorder.Code != expectedStatus {
t.Errorf("want %d, got %d", expectedStatus, recorder.Code)
}
if recorder.Body.String() != expectedBody {
t.Errorf("want %s, got %s", expectedBody, recorder.Body.String())
}
}
func TestFindNotFound(t *testing.T) {
t.Parallel()
// Arrange
expectedBody := `{ "message": "not found" }`
expectedStatus := http.StatusNotFound
m := &mock{find: func() (string, error) { return expectedBody, errors.New("not found") }}
server := NewServer(m)
recorder := httptest.NewRecorder()
// Act
server.Find(recorder, &http.Request{})
// Assert
if recorder.Code != expectedStatus {
t.Errorf("want %d, got %d", expectedStatus, recorder.Code)
}
if recorder.Body.String() != expectedBody {
t.Errorf("want %s, got %s", expectedBody, recorder.Body.String())
}
}
func TestAddOk(t *testing.T) {
t.Parallel()
// Arrange
expectedBody := `{ "message": "success" }`
expectedStatus := http.StatusOK
m := &mock{add: func(string) error { return nil }}
server := NewServer(m)
recorder := httptest.NewRecorder()
// Act
request, _ := http.NewRequest("GET", "/add?name=mike", nil)
server.Add(recorder, request)
// Assert
if recorder.Code != expectedStatus {
t.Errorf("want %d, got %d", expectedStatus, recorder.Code)
}
if recorder.Body.String() != expectedBody {
t.Errorf("want %s, got %s", expectedBody, recorder.Body.String())
}
}
go clean -testcache
go test ./...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With