I want to wrap goquery.Selection
for getting HTML and selector string more conveniently.
To access methods of goquery.Selection
, should I implement some helper method such as Get()
on the following code?
type MySelection goquery.Selection
// Without this helper method, I should always use type conversion
// to use goquery.Selection's methods.
func (s *MySelection) Get() *goquery.Selection {
sel := s.(goquery.Selection)
return sel
}
func (s *MySelection) HTML() string {
// html, _ := s.Html() <- error
html, _ := s.Get().Html()
return html
}
func (s *MySelection) String() string {
return fmt.Sprintf("%v#%v.%v",
goquery.NodeName(s.Get()),
s.Get().AttrOr("id", "(undefined)"),
s.Get().AttrOr("class", "(undefined)"))
}
Are there better ways to handle this situation?
You also can embed
type MySelection struct {
goquery.Selection
some payload //if needed
}
and you will have goquery.Selection methods for MySelection for free and can to add or overwrite some.
Well, there are several ways to "handle this." But don't name it Get()
: it isn't idiomatic.
From a best-practices perspective, I would recommend:
The reasons for this is many. But for Go, it's best to keep it simple - which boils down to one question: do you want to unit test your code?
If the answer is yes, then I would never use a 3rd party package directly. I'd wrap their package with my own interface. Then, use use (inject) that interface throughout all of my code so to allow me to mock it up in unit tests.
Again there are several patterns and opinions; but, i am going to show this one of a wrapper allowing for unit testing.
package mypackage
import (
"path/to/goquery.Selection"
)
var _mySelector *mySelector // Go stdlib uses underscores for private types
type mySelector interface {
Html() string
...
}
type MySelector struct {
}
func (ms *MySelector) Html() {
// your custom version
}
// initialize the global var with your wrapper
func init() {
_mySelector = &MySelector{ ... }
}
package mypackage
func Foo() {
// uses the global var initialized with init()
data := _mySelector.Html()
// IoC using D.I. through constructors
obj := NewSomething(_mySelector)
// IoC using D.I. through methods
result := bar.Process(_mySelector, "input data")
}
package mypackage
import (
"testing"
)
type mockSelector struct {
HtmlWasCalled bool
HtmlReturnsThis string
}
func (ms mockSelector) Html() string {
ms.HtmlWasCalled = true
return ms.HtmlReturnsThis
}
func TestBar(t *testing.T) {
// arrange
// override your global var
oldMS := _mySelector
_mySelector := &mockSelector{
HtmlReturnsThis: "<b>success</b>",
}
// act
// since foo.Bar is using the global var, it now uses
// our mock we set above.
result := foo.Bar("sample input")
// assert
if result != expected {
t.Fail()
}
// put it back the way it was
_mySelector = oldMS
}
func TestFoo(t *testing.T) {
// arrange
mock := &mockSelector{
HtmlReturnsThis: "<b>success</b>",
}
// act
// or, just inject your mock if using IoC
result := bar.Process(mock, "sample input")
// assert
...
}
This decouples me from having to deal with the 3rd party package nuances during unit testing. Works well, except when the API of the package is huge. Then, I question even why I am using the package to begin with if it is that complicated.