Say we have a struct and a constructor function for the structure like such
package myPackage
type Client struct {
a TypeA
b TypeB
}
func NewClient(a TypeA, b TypeB) ConcreteClient {
return &Client{
a: a,
b: b,
}
}
type ConcreteClient interface {
ExportedFunc()
}
func (c *Client) privateFunc() {
// ...
}
func (c *Client) ExportedFunc() {
// ...
}
And we use this client in a test package like such
var (
c = &Client {
a:a,
b:b,
}
)
func TestUnexported(t *testing.T) {
c.privateFunc() // Works
}
In the previous case the unexported is discovered as expected in the test file but when we use the constructor like this
var (
c = NewClient()
)
func TestUnexported(t *testing.T) {
c.privateFunc() // Doesn't work
}
The unexported method isn't exposed for testing. These files exist within the same package and follow the *_test naming pattern.I haven't been able to figure out exactly what is going on scoping wise as to why the unexported methods are hidden when created through a constructor and not through typical construction.
When you return a type as an interface or accept an interface in a function, then the type is reduced to only the methods in that interface and everything else will be inaccessible. You've effective "converted" the type from Client
to ConcreteClient
.
Consider how the code would look if all methods from all types be readily accessible:
func f(fp io.Reader) {
fp.Seek(42)
}
How annoying would such a function be to use: "you can pass any io.Reader
to this, but oh, it must also have the Seek()
method or you'll get compile errors".
This is why additional interfaces such as io.ReadSeeker
(which has Read()
and Seek()
) exist.
Not all is lost, you can still use access everything from the type methods:
func (c *Client) ExportedFunc() string {
return c.a
}
func main() {
cclient := NewClient("this is a", "this is b")
fmt.Printf(cclient.ExportedFunc())
}
Or you can use a type assertion to get the original type back:
cclient := NewClient("this is a", "this is b")
client, ok := cclient.(*Client)
if !ok {
fmt.Printf("not a Client: %T
", cclient)
os.Exit(1)
}
fmt.Printf("a: %v
", client.a)