How to solve the unit tester's dilemma

  • 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(
                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

    //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.",
            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

  • As a Go developer, I make heavy use of the mockery and testify packages. Using mocks is the industry-standard way for Go developers to achieve a reasonable level of code coverage.

    You might try showing your boss some of the problems that are hard to solve without resorting to mocks and without refactoring.

    If that doesn't work, you might consider changing bosses or finding a new job. There is plenty of demand for Go developers.

Log in to reply

Suggested Topics

  • 2
  • 2
  • 2
  • 2
  • 2
  • 2
  • 2
  • 2