I'm working on testing an API server SDK written in Golang. My literal job on this is solely unit-testing, documenting the code under test, and of course reporting any fails with the rest of the dev team.
There's a few methods under test that hit other methods under test, that may hit others, that I'm responsible for testing. For example, at the time of me writing this, I'm trying to test this method:
//SendRequest This is used to contact the apiserver synchronously.
func (apiPath *APIPath) SendRequest(context interface{}, tokenHandler *apiToken.APITokenHandlerSt,
header map[string]string,
urlParams []string, urlQueries url.Values,
jsonBody []byte) apiCore.CallResultSt {
if apiToken := tokenHandler.GetToken(apiPath.AuthType, apiPath.Scope); apiToken != nil {
return apiPath.APICoreHandler.SendRequest(
context,
apiToken.Token,
apiPath.GetPath(urlParams, urlQueries), apiPath.Type,
header, jsonBody)
} else {
errMsg, _ := json.Marshal(errors.InvalidAuthentication())
return apiCore.CallResultSt{DetailObject: errMsg, IsSucceeded: false}
}
}
where the receiver struct is defined to be
//APIPath=======================
//Used for url construction
type APIPath struct {
APICoreHandler *apiCore.APICoreSt
// domain name of API
DomainPath string
ParentAPI *APIPath
Type apiCore.APIType
// subfunction name
SubFunc string
KeyName string
AutoAddKeyToPath bool
AuthType oAuth2.OAuth2Type
Scope string
}
Note that method has an external dependency. It is defined thus:
//Establish the request to send to the server
func (a *APICoreSt) SendRequest(context interface{}, token string, apiURL string, callType APIType, header map[string]string, jsonBody []byte) CallResultSt {
if header == nil {
header = make(map[string]string)
}
if header["Authorization"] == "" {
header["Authorization"] = "Bearer " + token
}
header["Scope"] = GeneralScope
header["Content-Type"] = "application/json; charset=UTF-8"
return a.CallServer(context, callType, apiURL, header, jsonBody)
}
and that method ends up hitting this one:
//CallServer Calls the server
//
// Parameters:
// - `context` : a context to pass to the server
// - `apiType` : the HTTP method (`GET`,`POST`,`PUT`,`DELETE`,...)
// - `apiURL` : the URL to hit
// - `header` : request header
// - `jsonBody`: the JSON body to send
//
// Returns:
// - a CallResultSt. This CallResultSt might have an error for its `DetailObject`
func (a *APICoreSt) CallServer(context interface{}, apiType APIType, apiURL string, header map[string]string, jsonBody []byte) CallResultSt {
var (
Url = a.BaseURL + apiURL
err error
res *http.Response
resBody json.RawMessage
hc = &http.Client{}
req = new(http.Request)
)
req, err = http.NewRequest(string(apiType), Url, bytes.NewBuffer(jsonBody))
if err != nil {
//Use a map instead of errorSt so that it doesn't create a heavy dependency.
errorSt := map[string]string{
"Code": "ez020300007",
"Message": "The request failed to be created.",
}
logger.Instance.LogError(err.Error())
err, _ := json.Marshal(errorSt)
return CallResultSt{DetailObject: err, IsSucceeded: false}
}
for k, v := range header {
req.Header.Set(k, v)
}
res, err = hc.Do(req)
if res != nil {
resBody, err = ioutil.ReadAll(res.Body)
res.Body = ioutil.NopCloser(bytes.NewBuffer(resBody))
}
return CallResultSt{resBody, logger.Instance.CheckAndHandleErr(context, res)}
}
(The struct these two methods are on is omitted for brevity: there's only one field of that struct that it relies on.)
These type of situations keep popping up in the code under test, and yesterday, I confronted the boss about this, citing these dependencies, and the fact that there's no struct makes them literally impossible to test-double (fake,stub,mock). I was merely asking to refactor it so that it hits an interface method, declared in the same file as the dependency, which those struct methods would implement (implementing interfaces is implicit in Golang). It turned into a bit of an argument, and he brought the talking point "What do you do about third-party dependencies and built-in dependencies?" I told him that there's usually a way to test-double those concerns out (fakes, mocks, stubs), but he wouldn't budge.
What to do about this situation to convince him that this refactoring is indeed necessary, and failing that, what should I do about this situation?
UPDATE: The method under test has other business dependencies, defined thus:
// GetToken returns the token having the specified `tokenType` and `scope`
//
// Parameters:
// - `tokenType`
// - `scope`
//
// Returns:
// - pointer to Token having `tokenType`,`scope` or nil
func (ath *APITokenHandlerSt) GetToken(tokenType oAuth2.OAuth2Type, scope string) *APITokenSt {
if ath == nil {
return nil
}
if i := ath.FindToken(tokenType, scope); i == -1 {
return nil
} else {
return &ath.Tokens[i]
}
}
// FindToken finds token with specified `scope` and `tokenType`
//
// Parameters:
// - `tokenType` : the `OAuth2Type` of the token we want
// - `scope`: the scope of the token we want
//
// Returns:
// - the index of the token in ath.Tokens, or -1 if it cannot be found
func (ath *APITokenHandlerSt) FindToken(tokenType oAuth2.OAuth2Type, scope string) int {
for i, t := range ath.Tokens {
if t.TokenType == tokenType &&
t.Scope == scope {
return i
}
}
return -1
}